@intentius/sql-yodeler 0.0.0-stage → 0.2.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/LICENSE +202 -0
- package/README.md +112 -2
- package/dist/bin.d.ts +2 -0
- package/dist/bin.js +29 -0
- package/dist/bin.js.map +1 -0
- package/dist/chant/apply.d.ts +59 -0
- package/dist/chant/apply.js +116 -0
- package/dist/chant/apply.js.map +1 -0
- package/dist/chant/build.d.ts +34 -0
- package/dist/chant/build.js +48 -0
- package/dist/chant/build.js.map +1 -0
- package/dist/chant/drift.d.ts +112 -0
- package/dist/chant/drift.js +238 -0
- package/dist/chant/drift.js.map +1 -0
- package/dist/chant/plan.d.ts +45 -0
- package/dist/chant/plan.js +61 -0
- package/dist/chant/plan.js.map +1 -0
- package/dist/chant/process.d.ts +41 -0
- package/dist/chant/process.js +87 -0
- package/dist/chant/process.js.map +1 -0
- package/dist/chant/runnable.d.ts +19 -0
- package/dist/chant/runnable.js +47 -0
- package/dist/chant/runnable.js.map +1 -0
- package/dist/cli.d.ts +8 -0
- package/dist/cli.js +99 -0
- package/dist/cli.js.map +1 -0
- package/dist/command.d.ts +23 -0
- package/dist/command.js +13 -0
- package/dist/command.js.map +1 -0
- package/dist/commands/apply.d.ts +3 -0
- package/dist/commands/apply.js +426 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/approve.d.ts +5 -0
- package/dist/commands/approve.js +111 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/drift.d.ts +14 -0
- package/dist/commands/drift.js +267 -0
- package/dist/commands/drift.js.map +1 -0
- package/dist/commands/index.d.ts +3 -0
- package/dist/commands/index.js +28 -0
- package/dist/commands/index.js.map +1 -0
- package/dist/commands/init.d.ts +6 -0
- package/dist/commands/init.js +139 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/lint.d.ts +3 -0
- package/dist/commands/lint.js +202 -0
- package/dist/commands/lint.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.js +50 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/new.d.ts +5 -0
- package/dist/commands/new.js +292 -0
- package/dist/commands/new.js.map +1 -0
- package/dist/commands/override.d.ts +3 -0
- package/dist/commands/override.js +118 -0
- package/dist/commands/override.js.map +1 -0
- package/dist/commands/plan.d.ts +3 -0
- package/dist/commands/plan.js +183 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/rebase.d.ts +3 -0
- package/dist/commands/rebase.js +167 -0
- package/dist/commands/rebase.js.map +1 -0
- package/dist/commands/repair.d.ts +14 -0
- package/dist/commands/repair.js +125 -0
- package/dist/commands/repair.js.map +1 -0
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.js +54 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/config/define.d.ts +148 -0
- package/dist/config/define.js +20 -0
- package/dist/config/define.js.map +1 -0
- package/dist/config/index.d.ts +11 -0
- package/dist/config/index.js +12 -0
- package/dist/config/index.js.map +1 -0
- package/dist/config/load.d.ts +17 -0
- package/dist/config/load.js +162 -0
- package/dist/config/load.js.map +1 -0
- package/dist/config/settings.d.ts +53 -0
- package/dist/config/settings.js +116 -0
- package/dist/config/settings.js.map +1 -0
- package/dist/env-args.d.ts +24 -0
- package/dist/env-args.js +92 -0
- package/dist/env-args.js.map +1 -0
- package/dist/exit.d.ts +20 -0
- package/dist/exit.js +28 -0
- package/dist/exit.js.map +1 -0
- package/dist/history/client.d.ts +25 -0
- package/dist/history/client.js +42 -0
- package/dist/history/client.js.map +1 -0
- package/dist/history/ddl.d.ts +58 -0
- package/dist/history/ddl.js +100 -0
- package/dist/history/ddl.js.map +1 -0
- package/dist/history/index.d.ts +25 -0
- package/dist/history/index.js +26 -0
- package/dist/history/index.js.map +1 -0
- package/dist/history/lock.d.ts +104 -0
- package/dist/history/lock.js +230 -0
- package/dist/history/lock.js.map +1 -0
- package/dist/history/postgres/ddl.d.ts +22 -0
- package/dist/history/postgres/ddl.js +47 -0
- package/dist/history/postgres/ddl.js.map +1 -0
- package/dist/history/postgres/index.d.ts +12 -0
- package/dist/history/postgres/index.js +13 -0
- package/dist/history/postgres/index.js.map +1 -0
- package/dist/history/postgres/lock.d.ts +48 -0
- package/dist/history/postgres/lock.js +95 -0
- package/dist/history/postgres/lock.js.map +1 -0
- package/dist/history/postgres/store.d.ts +39 -0
- package/dist/history/postgres/store.js +68 -0
- package/dist/history/postgres/store.js.map +1 -0
- package/dist/history/rows.d.ts +96 -0
- package/dist/history/rows.js +73 -0
- package/dist/history/rows.js.map +1 -0
- package/dist/history/store.d.ts +72 -0
- package/dist/history/store.js +134 -0
- package/dist/history/store.js.map +1 -0
- package/dist/history/topology.d.ts +41 -0
- package/dist/history/topology.js +54 -0
- package/dist/history/topology.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/init/adopt.d.ts +70 -0
- package/dist/init/adopt.js +143 -0
- package/dist/init/adopt.js.map +1 -0
- package/dist/init/baseline.d.ts +61 -0
- package/dist/init/baseline.js +88 -0
- package/dist/init/baseline.js.map +1 -0
- package/dist/init/files.d.ts +23 -0
- package/dist/init/files.js +77 -0
- package/dist/init/files.js.map +1 -0
- package/dist/init/index.d.ts +16 -0
- package/dist/init/index.js +17 -0
- package/dist/init/index.js.map +1 -0
- package/dist/init/live.d.ts +39 -0
- package/dist/init/live.js +68 -0
- package/dist/init/live.js.map +1 -0
- package/dist/lint/check.d.ts +88 -0
- package/dist/lint/check.js +227 -0
- package/dist/lint/check.js.map +1 -0
- package/dist/lint/config.d.ts +21 -0
- package/dist/lint/config.js +43 -0
- package/dist/lint/config.js.map +1 -0
- package/dist/lint/index.d.ts +31 -0
- package/dist/lint/index.js +43 -0
- package/dist/lint/index.js.map +1 -0
- package/dist/lint/project-rules.d.ts +19 -0
- package/dist/lint/project-rules.js +32 -0
- package/dist/lint/project-rules.js.map +1 -0
- package/dist/lint/report.d.ts +7 -0
- package/dist/lint/report.js +48 -0
- package/dist/lint/report.js.map +1 -0
- package/dist/lint/rules.d.ts +53 -0
- package/dist/lint/rules.js +42 -0
- package/dist/lint/rules.js.map +1 -0
- package/dist/lint/silences.d.ts +49 -0
- package/dist/lint/silences.js +91 -0
- package/dist/lint/silences.js.map +1 -0
- package/dist/lint/statements.d.ts +38 -0
- package/dist/lint/statements.js +120 -0
- package/dist/lint/statements.js.map +1 -0
- package/dist/mcp/server.d.ts +40 -0
- package/dist/mcp/server.js +106 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +72 -0
- package/dist/mcp/tools.js +178 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/migrations/checks.d.ts +28 -0
- package/dist/migrations/checks.js +97 -0
- package/dist/migrations/checks.js.map +1 -0
- package/dist/migrations/checksum.d.ts +56 -0
- package/dist/migrations/checksum.js +72 -0
- package/dist/migrations/checksum.js.map +1 -0
- package/dist/migrations/create.d.ts +75 -0
- package/dist/migrations/create.js +120 -0
- package/dist/migrations/create.js.map +1 -0
- package/dist/migrations/directory.d.ts +119 -0
- package/dist/migrations/directory.js +229 -0
- package/dist/migrations/directory.js.map +1 -0
- package/dist/migrations/document.d.ts +106 -0
- package/dist/migrations/document.js +83 -0
- package/dist/migrations/document.js.map +1 -0
- package/dist/migrations/index.d.ts +21 -0
- package/dist/migrations/index.js +22 -0
- package/dist/migrations/index.js.map +1 -0
- package/dist/pr/comment.d.ts +32 -0
- package/dist/pr/comment.js +99 -0
- package/dist/pr/comment.js.map +1 -0
- package/dist/pr/forge.d.ts +54 -0
- package/dist/pr/forge.js +119 -0
- package/dist/pr/forge.js.map +1 -0
- package/dist/pr/issue.d.ts +68 -0
- package/dist/pr/issue.js +107 -0
- package/dist/pr/issue.js.map +1 -0
- package/dist/pr/model.d.ts +99 -0
- package/dist/pr/model.js +137 -0
- package/dist/pr/model.js.map +1 -0
- package/dist/pr/render.d.ts +29 -0
- package/dist/pr/render.js +279 -0
- package/dist/pr/render.js.map +1 -0
- package/dist/project.d.ts +62 -0
- package/dist/project.js +92 -0
- package/dist/project.js.map +1 -0
- package/dist/rebase/index.d.ts +121 -0
- package/dist/rebase/index.js +390 -0
- package/dist/rebase/index.js.map +1 -0
- package/dist/rebase/repair.d.ts +41 -0
- package/dist/rebase/repair.js +68 -0
- package/dist/rebase/repair.js.map +1 -0
- package/dist/replay/clickhouse.d.ts +33 -0
- package/dist/replay/clickhouse.js +106 -0
- package/dist/replay/clickhouse.js.map +1 -0
- package/dist/replay/index.d.ts +39 -0
- package/dist/replay/index.js +57 -0
- package/dist/replay/index.js.map +1 -0
- package/dist/replay/postgres.d.ts +31 -0
- package/dist/replay/postgres.js +140 -0
- package/dist/replay/postgres.js.map +1 -0
- package/dist/replay/replay.d.ts +114 -0
- package/dist/replay/replay.js +98 -0
- package/dist/replay/replay.js.map +1 -0
- package/dist/replay/report.d.ts +18 -0
- package/dist/replay/report.js +72 -0
- package/dist/replay/report.js.map +1 -0
- package/dist/replay/shared.d.ts +28 -0
- package/dist/replay/shared.js +77 -0
- package/dist/replay/shared.js.map +1 -0
- package/dist/schemas.d.ts +34 -0
- package/dist/schemas.js +38 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +40 -0
- package/dist/server.js +87 -0
- package/dist/server.js.map +1 -0
- package/dist/steps/backfill.d.ts +132 -0
- package/dist/steps/backfill.js +307 -0
- package/dist/steps/backfill.js.map +1 -0
- package/dist/steps/index.d.ts +52 -0
- package/dist/steps/index.js +52 -0
- package/dist/steps/index.js.map +1 -0
- package/dist/steps/kinds.d.ts +101 -0
- package/dist/steps/kinds.js +143 -0
- package/dist/steps/kinds.js.map +1 -0
- package/dist/steps/postgres-migration.d.ts +95 -0
- package/dist/steps/postgres-migration.js +149 -0
- package/dist/steps/postgres-migration.js.map +1 -0
- package/dist/steps/rebuild.d.ts +97 -0
- package/dist/steps/rebuild.js +155 -0
- package/dist/steps/rebuild.js.map +1 -0
- package/dist/steps/run.d.ts +45 -0
- package/dist/steps/run.js +72 -0
- package/dist/steps/run.js.map +1 -0
- package/dist/topology/render.d.ts +17 -0
- package/dist/topology/render.js +17 -0
- package/dist/topology/render.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +11 -0
- package/dist/version.js.map +1 -0
- package/dist/versioned/approval.d.ts +35 -0
- package/dist/versioned/approval.js +53 -0
- package/dist/versioned/approval.js.map +1 -0
- package/dist/versioned/checks.d.ts +29 -0
- package/dist/versioned/checks.js +54 -0
- package/dist/versioned/checks.js.map +1 -0
- package/dist/versioned/clickhouse.d.ts +24 -0
- package/dist/versioned/clickhouse.js +42 -0
- package/dist/versioned/clickhouse.js.map +1 -0
- package/dist/versioned/digest.d.ts +77 -0
- package/dist/versioned/digest.js +69 -0
- package/dist/versioned/digest.js.map +1 -0
- package/dist/versioned/driver.d.ts +51 -0
- package/dist/versioned/driver.js +13 -0
- package/dist/versioned/driver.js.map +1 -0
- package/dist/versioned/engine.d.ts +206 -0
- package/dist/versioned/engine.js +370 -0
- package/dist/versioned/engine.js.map +1 -0
- package/dist/versioned/explain.d.ts +123 -0
- package/dist/versioned/explain.js +294 -0
- package/dist/versioned/explain.js.map +1 -0
- package/dist/versioned/index.d.ts +46 -0
- package/dist/versioned/index.js +77 -0
- package/dist/versioned/index.js.map +1 -0
- package/dist/versioned/op.d.ts +72 -0
- package/dist/versioned/op.js +185 -0
- package/dist/versioned/op.js.map +1 -0
- package/dist/versioned/policy.d.ts +67 -0
- package/dist/versioned/policy.js +161 -0
- package/dist/versioned/policy.js.map +1 -0
- package/dist/versioned/postgres/catalog.d.ts +33 -0
- package/dist/versioned/postgres/catalog.js +34 -0
- package/dist/versioned/postgres/catalog.js.map +1 -0
- package/dist/versioned/postgres/driver.d.ts +71 -0
- package/dist/versioned/postgres/driver.js +140 -0
- package/dist/versioned/postgres/driver.js.map +1 -0
- package/dist/versioned/postgres/executor.d.ts +104 -0
- package/dist/versioned/postgres/executor.js +172 -0
- package/dist/versioned/postgres/executor.js.map +1 -0
- package/dist/versioned/postgres/index.d.ts +11 -0
- package/dist/versioned/postgres/index.js +12 -0
- package/dist/versioned/postgres/index.js.map +1 -0
- package/dist/versioned/postgres/waits.d.ts +46 -0
- package/dist/versioned/postgres/waits.js +78 -0
- package/dist/versioned/postgres/waits.js.map +1 -0
- package/dist/versioned/report.d.ts +25 -0
- package/dist/versioned/report.js +84 -0
- package/dist/versioned/report.js.map +1 -0
- package/dist/versioned/stand-down.d.ts +30 -0
- package/dist/versioned/stand-down.js +56 -0
- package/dist/versioned/stand-down.js.map +1 -0
- package/dist/versioned/state.d.ts +108 -0
- package/dist/versioned/state.js +153 -0
- package/dist/versioned/state.js.map +1 -0
- package/package.json +61 -4
- package/schemas/apply.schema.json +269 -0
- package/schemas/common.schema.json +213 -0
- package/schemas/drift.schema.json +36 -0
- package/schemas/init.schema.json +31 -0
- package/schemas/lint-rules.schema.json +54 -0
- package/schemas/lint.schema.json +290 -0
- package/schemas/mcp.schema.json +70 -0
- package/schemas/new.schema.json +71 -0
- package/schemas/plan.schema.json +371 -0
- package/schemas/rebase.schema.json +54 -0
- package/schemas/repair.schema.json +20 -0
- package/schemas/status.schema.json +310 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The versioned apply on Postgres (#13): binding an environment's server,
|
|
3
|
+
* its history, its lock and its statements.
|
|
4
|
+
*
|
|
5
|
+
* Settings:
|
|
6
|
+
*
|
|
7
|
+
* the environment's history database (`yodel.config.ts`
|
|
8
|
+
* `environments.<env>.history.database`, else YODEL_HISTORY_DATABASE,
|
|
9
|
+
* default `yodeler`) is the history's schema: `<schema>.history`, and the lock's key
|
|
10
|
+
* YODEL_LOCK_RETRIES how many more times a statement that hit the lock timeout is tried (default 5)
|
|
11
|
+
*
|
|
12
|
+
* and from `sql.profiles.<env>`, as chant's applier reads them:
|
|
13
|
+
*
|
|
14
|
+
* lockTimeoutMs lock_timeout for every statement, and for the live-schema read (default 5000)
|
|
15
|
+
* statementTimeoutMs statement_timeout for a catalog change (default 60000)
|
|
16
|
+
* scanTimeoutMs statement_timeout for a statement that reads or rewrites rows (default none)
|
|
17
|
+
* schemas the schemas the plan digest's fingerprint covers (default every schema but the server's own)
|
|
18
|
+
*
|
|
19
|
+
* Two connections: the work connection, opened on first use, on which the
|
|
20
|
+
* history is read and written and the statements run; and the lock's,
|
|
21
|
+
* opened when the lock is taken and closed when it is released.
|
|
22
|
+
*/
|
|
23
|
+
import { YodelError } from "../../project.js";
|
|
24
|
+
import { DEFAULT_HISTORY_DATABASE } from "../../history/index.js";
|
|
25
|
+
import { acquireAdvisoryLock, pgIdent, PostgresHistoryStore } from "../../history/postgres/index.js";
|
|
26
|
+
import { pgCatalogFingerprint, readPgCatalog } from "./catalog.js";
|
|
27
|
+
import { readUnderLockTimeout } from "./waits.js";
|
|
28
|
+
import { DEFAULT_LOCK_RETRIES, DEFAULT_PG_TIMEOUTS, PgStatementError, runPgStatement } from "./executor.js";
|
|
29
|
+
export function pgSettings(target, env = process.env, historySchema = env.YODEL_HISTORY_DATABASE || DEFAULT_HISTORY_DATABASE) {
|
|
30
|
+
const retries = env.YODEL_LOCK_RETRIES ? Number(env.YODEL_LOCK_RETRIES) : DEFAULT_LOCK_RETRIES;
|
|
31
|
+
if (!(Number.isInteger(retries) && retries >= 0))
|
|
32
|
+
throw new YodelError(`YODEL_LOCK_RETRIES must be a whole number of retries, got ${JSON.stringify(env.YODEL_LOCK_RETRIES)}`);
|
|
33
|
+
const t = target.timeouts ?? {};
|
|
34
|
+
return {
|
|
35
|
+
historySchema,
|
|
36
|
+
timeouts: {
|
|
37
|
+
lockTimeoutMs: t.lockTimeoutMs ?? DEFAULT_PG_TIMEOUTS.lockTimeoutMs,
|
|
38
|
+
statementTimeoutMs: t.statementTimeoutMs ?? DEFAULT_PG_TIMEOUTS.statementTimeoutMs,
|
|
39
|
+
scanTimeoutMs: t.scanTimeoutMs ?? DEFAULT_PG_TIMEOUTS.scanTimeoutMs,
|
|
40
|
+
},
|
|
41
|
+
lockRetries: retries,
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
const chantConnect = async (endpoint, applicationName) => {
|
|
45
|
+
const { connectPostgres } = await import("@intentius/chant-lexicon-sql/postgres/live/client");
|
|
46
|
+
return connectPostgres(endpoint, { applicationName });
|
|
47
|
+
};
|
|
48
|
+
/** A connection opened on its first query, so a verb that reads nothing opens none. */
|
|
49
|
+
export class LazyConnection {
|
|
50
|
+
open;
|
|
51
|
+
conn;
|
|
52
|
+
constructor(open) {
|
|
53
|
+
this.open = open;
|
|
54
|
+
}
|
|
55
|
+
async query(sql, params) {
|
|
56
|
+
this.conn ??= this.open();
|
|
57
|
+
return (await this.conn).query(sql, params);
|
|
58
|
+
}
|
|
59
|
+
async end() {
|
|
60
|
+
const c = this.conn;
|
|
61
|
+
this.conn = undefined;
|
|
62
|
+
if (c)
|
|
63
|
+
await (await c.catch(() => undefined))?.end();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/** Binds `env`'s Postgres server (`sql.profiles.<env>`) and its history. Connects on first use. */
|
|
67
|
+
export async function openPostgres(project, env, processEnv = process.env, historySchema, connect = chantConnect) {
|
|
68
|
+
const { resolvePostgresTarget, isUnresolvedTarget } = await import("@intentius/chant-lexicon-sql/postgres/live/bind");
|
|
69
|
+
const target = resolvePostgresTarget({ environment: env.name, config: project.config, env: processEnv });
|
|
70
|
+
if (isUnresolvedTarget(target))
|
|
71
|
+
throw new YodelError(target.detail);
|
|
72
|
+
const settings = pgSettings(target, processEnv, historySchema);
|
|
73
|
+
const client = new LazyConnection(() => connect(target.endpoint, "yodel"));
|
|
74
|
+
const store = new PostgresHistoryStore(client, { schema: settings.historySchema });
|
|
75
|
+
const schemas = target.schemas;
|
|
76
|
+
const driver = postgresDriver({ client, connect: () => connect(target.endpoint, "yodel lock"), settings, schemas, store });
|
|
77
|
+
return { endpoint: target.endpoint, schemas, defaultSchema: target.defaultSchema, settings, client, store, driver };
|
|
78
|
+
}
|
|
79
|
+
export function postgresDriver(input) {
|
|
80
|
+
const { client, settings } = input;
|
|
81
|
+
return {
|
|
82
|
+
dialect: "postgres",
|
|
83
|
+
async acquireLock(holder) {
|
|
84
|
+
return acquireAdvisoryLock(await input.connect(), { schema: settings.historySchema, holder });
|
|
85
|
+
},
|
|
86
|
+
async liveFingerprint() {
|
|
87
|
+
const read = () => readPgCatalog(client, { schemas: input.schemas, historySchema: settings.historySchema });
|
|
88
|
+
const entries = await readUnderLockTimeout(client, read, {
|
|
89
|
+
lockTimeoutMs: settings.timeouts.lockTimeoutMs,
|
|
90
|
+
retries: settings.lockRetries,
|
|
91
|
+
schemas: input.schemas,
|
|
92
|
+
what: "the live schema",
|
|
93
|
+
log: input.log ?? ((line) => process.stderr.write(`yodel: ${line}\n`)),
|
|
94
|
+
...(input.sleep ? { sleep: input.sleep } : {}),
|
|
95
|
+
...(input.retryBaseMs !== undefined ? { retryBaseMs: input.retryBaseMs } : {}),
|
|
96
|
+
});
|
|
97
|
+
return pgCatalogFingerprint(entries);
|
|
98
|
+
},
|
|
99
|
+
async readCheck(sql, where) {
|
|
100
|
+
await client.query("BEGIN READ ONLY");
|
|
101
|
+
try {
|
|
102
|
+
if (where.defaultSchema)
|
|
103
|
+
await client.query(`SELECT pg_catalog.set_config('search_path', $1, true)`, [pgIdent(where.defaultSchema)]);
|
|
104
|
+
// A check reads rows: the scan timeout (default none), and the lock timeout every statement gets.
|
|
105
|
+
await client.query(`SELECT pg_catalog.set_config('statement_timeout', $1, true), pg_catalog.set_config('lock_timeout', $2, true)`, [
|
|
106
|
+
String(settings.timeouts.scanTimeoutMs),
|
|
107
|
+
String(settings.timeouts.lockTimeoutMs),
|
|
108
|
+
]);
|
|
109
|
+
return await client.query(sql);
|
|
110
|
+
}
|
|
111
|
+
finally {
|
|
112
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
async runStatement(step, run) {
|
|
116
|
+
try {
|
|
117
|
+
await runPgStatement(client, step, {
|
|
118
|
+
timeouts: settings.timeouts,
|
|
119
|
+
retries: settings.lockRetries,
|
|
120
|
+
succeeded: async (note) => {
|
|
121
|
+
await run.succeeded(note);
|
|
122
|
+
},
|
|
123
|
+
log: run.log,
|
|
124
|
+
...(input.sleep ? { sleep: input.sleep } : {}),
|
|
125
|
+
...(input.retryBaseMs !== undefined ? { retryBaseMs: input.retryBaseMs } : {}),
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
catch (e) {
|
|
129
|
+
if (e instanceof PgStatementError)
|
|
130
|
+
throw new Error(e.error, { cause: e });
|
|
131
|
+
throw e;
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
stepContext() {
|
|
135
|
+
return { lockTimeoutMs: settings.timeouts.lockTimeoutMs, postgres: { client, timeouts: settings.timeouts, receiptsSchema: settings.historySchema } };
|
|
136
|
+
},
|
|
137
|
+
close: () => client.end(),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=driver.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"driver.js","sourceRoot":"","sources":["../../../src/versioned/postgres/driver.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sending one migration statement to Postgres, the way chant's applier does
|
|
3
|
+
* (`postgres/apply/apply.ts`), with the history row that says it succeeded.
|
|
4
|
+
*
|
|
5
|
+
* ## Timeouts and retries
|
|
6
|
+
*
|
|
7
|
+
* Every statement runs with its own `lock_timeout` (default 5 s, the
|
|
8
|
+
* profile's `lockTimeoutMs`) and `statement_timeout` (the profile's
|
|
9
|
+
* `statementTimeoutMs`, default 60 s, for a catalog change; `scanTimeoutMs`,
|
|
10
|
+
* default none, for one that reads or rewrites rows). A statement blocked
|
|
11
|
+
* behind another session's lock gives up when the lock timeout passes
|
|
12
|
+
* (SQLSTATE 55P03) instead of queueing every later query on the table
|
|
13
|
+
* behind it, and is tried again after a pause that doubles each time (250
|
|
14
|
+
* ms, 500 ms, ...), up to `retries` more times (default 5). Any other error
|
|
15
|
+
* fails it at once.
|
|
16
|
+
*
|
|
17
|
+
* ## Transactions
|
|
18
|
+
*
|
|
19
|
+
* A statement chant marks `transactional` runs in a transaction of its own:
|
|
20
|
+
*
|
|
21
|
+
* BEGIN; SET LOCAL lock_timeout ...; SET LOCAL statement_timeout ...;
|
|
22
|
+
* <statement>; INSERT INTO <history> (... 'succeeded' ...); COMMIT
|
|
23
|
+
*
|
|
24
|
+
* so the statement and its `succeeded` row commit together or not at all.
|
|
25
|
+
* A runner killed anywhere in it leaves either both or neither: a statement
|
|
26
|
+
* whose latest row is `started` did not commit, and is sent again safely.
|
|
27
|
+
*
|
|
28
|
+
* A statement that may not run in a transaction block (`CREATE INDEX
|
|
29
|
+
* CONCURRENTLY`, `DROP INDEX CONCURRENTLY`) or that chant keeps out of one
|
|
30
|
+
* (`ALTER TYPE ... ADD VALUE`, whose label cannot be used in the transaction
|
|
31
|
+
* that adds it) runs alone, with `SET` for the session first; its
|
|
32
|
+
* `succeeded` row is written right after it, in a transaction of its own.
|
|
33
|
+
* A runner killed between the two leaves the statement run and its latest
|
|
34
|
+
* row `started`, and the next run sends it again.
|
|
35
|
+
*
|
|
36
|
+
* ## A failed CONCURRENTLY build
|
|
37
|
+
*
|
|
38
|
+
* `CREATE INDEX CONCURRENTLY` that fails (a lock timeout, a duplicate on a
|
|
39
|
+
* unique index, a cancel) leaves the index behind, INVALID: it is in the
|
|
40
|
+
* catalog, is maintained on every write, and is never used, and the next
|
|
41
|
+
* `CREATE INDEX` of that name fails because it exists. So before every
|
|
42
|
+
* attempt, the first, a retry, or a resume in a later run, and after every
|
|
43
|
+
* failure, an INVALID index of that name is dropped (`DROP INDEX
|
|
44
|
+
* CONCURRENTLY IF EXISTS`), and only an INVALID one: a valid index of that
|
|
45
|
+
* name is never touched.
|
|
46
|
+
*/
|
|
47
|
+
import { YodelError } from "../../project.js";
|
|
48
|
+
import type { PgClient } from "../../history/postgres/index.js";
|
|
49
|
+
/** SQLSTATE lock_not_available: the lock timeout passed. */
|
|
50
|
+
export declare const LOCK_NOT_AVAILABLE = "55P03";
|
|
51
|
+
export interface PgTimeouts {
|
|
52
|
+
lockTimeoutMs: number;
|
|
53
|
+
/** For a statement that changes only the catalog. 0 is no limit. */
|
|
54
|
+
statementTimeoutMs: number;
|
|
55
|
+
/** For a statement that reads or rewrites rows. 0 is no limit. */
|
|
56
|
+
scanTimeoutMs: number;
|
|
57
|
+
}
|
|
58
|
+
export declare const DEFAULT_PG_TIMEOUTS: PgTimeouts;
|
|
59
|
+
export declare const DEFAULT_LOCK_RETRIES = 5;
|
|
60
|
+
export declare const DEFAULT_RETRY_BASE_MS = 250;
|
|
61
|
+
/** The parts of a migration's statement step the executor reads. */
|
|
62
|
+
export interface PgStatement {
|
|
63
|
+
sql: string;
|
|
64
|
+
/** chant's class: `rewrite`, `validate` and `concurrently` read or rewrite rows. */
|
|
65
|
+
class: string;
|
|
66
|
+
/** False: outside any transaction. */
|
|
67
|
+
transactional: boolean;
|
|
68
|
+
}
|
|
69
|
+
export interface PgStatementOptions {
|
|
70
|
+
timeouts: PgTimeouts;
|
|
71
|
+
/** How many more times a statement that hit the lock timeout is tried. */
|
|
72
|
+
retries: number;
|
|
73
|
+
/** The first pause before a retry, in ms; it doubles each time. */
|
|
74
|
+
retryBaseMs?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Writes the `succeeded` row on the client, given the note it carries
|
|
77
|
+
* (the retries it took, the INVALID index it dropped). Called inside the
|
|
78
|
+
* statement's transaction when it has one.
|
|
79
|
+
*/
|
|
80
|
+
succeeded: (note: string) => Promise<void>;
|
|
81
|
+
log: (line: string) => void;
|
|
82
|
+
sleep?: (ms: number) => Promise<void>;
|
|
83
|
+
}
|
|
84
|
+
export interface PgStatementResult {
|
|
85
|
+
attempts: number;
|
|
86
|
+
/** INVALID indexes dropped before or between attempts. */
|
|
87
|
+
droppedInvalid: string[];
|
|
88
|
+
note: string;
|
|
89
|
+
}
|
|
90
|
+
/** A statement failed; `error` is the server's message. */
|
|
91
|
+
export declare class PgStatementError extends YodelError {
|
|
92
|
+
readonly error: string;
|
|
93
|
+
readonly code: string | undefined;
|
|
94
|
+
readonly attempts: number;
|
|
95
|
+
constructor(error: string, code: string | undefined, attempts: number);
|
|
96
|
+
}
|
|
97
|
+
/** Whether a statement of `cls` reads or rewrites rows (chant's `scansRows`), so it runs with the scan timeout. */
|
|
98
|
+
export declare const scansRows: (cls: string) => boolean;
|
|
99
|
+
/** The index a `CREATE INDEX CONCURRENTLY` builds, `schema.name` quoted, when the statement names its table's schema. */
|
|
100
|
+
export declare function concurrentIndex(sql: string): string | undefined;
|
|
101
|
+
/** Drops `index` when it is there and INVALID. Returns whether it dropped it. */
|
|
102
|
+
export declare function dropInvalidIndex(client: PgClient, index: string, timeouts: PgTimeouts, log: (line: string) => void): Promise<boolean>;
|
|
103
|
+
/** Runs one statement to success or failure; see the module comment. */
|
|
104
|
+
export declare function runPgStatement(client: PgClient, step: PgStatement, options: PgStatementOptions): Promise<PgStatementResult>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sending one migration statement to Postgres, the way chant's applier does
|
|
3
|
+
* (`postgres/apply/apply.ts`), with the history row that says it succeeded.
|
|
4
|
+
*
|
|
5
|
+
* ## Timeouts and retries
|
|
6
|
+
*
|
|
7
|
+
* Every statement runs with its own `lock_timeout` (default 5 s, the
|
|
8
|
+
* profile's `lockTimeoutMs`) and `statement_timeout` (the profile's
|
|
9
|
+
* `statementTimeoutMs`, default 60 s, for a catalog change; `scanTimeoutMs`,
|
|
10
|
+
* default none, for one that reads or rewrites rows). A statement blocked
|
|
11
|
+
* behind another session's lock gives up when the lock timeout passes
|
|
12
|
+
* (SQLSTATE 55P03) instead of queueing every later query on the table
|
|
13
|
+
* behind it, and is tried again after a pause that doubles each time (250
|
|
14
|
+
* ms, 500 ms, ...), up to `retries` more times (default 5). Any other error
|
|
15
|
+
* fails it at once.
|
|
16
|
+
*
|
|
17
|
+
* ## Transactions
|
|
18
|
+
*
|
|
19
|
+
* A statement chant marks `transactional` runs in a transaction of its own:
|
|
20
|
+
*
|
|
21
|
+
* BEGIN; SET LOCAL lock_timeout ...; SET LOCAL statement_timeout ...;
|
|
22
|
+
* <statement>; INSERT INTO <history> (... 'succeeded' ...); COMMIT
|
|
23
|
+
*
|
|
24
|
+
* so the statement and its `succeeded` row commit together or not at all.
|
|
25
|
+
* A runner killed anywhere in it leaves either both or neither: a statement
|
|
26
|
+
* whose latest row is `started` did not commit, and is sent again safely.
|
|
27
|
+
*
|
|
28
|
+
* A statement that may not run in a transaction block (`CREATE INDEX
|
|
29
|
+
* CONCURRENTLY`, `DROP INDEX CONCURRENTLY`) or that chant keeps out of one
|
|
30
|
+
* (`ALTER TYPE ... ADD VALUE`, whose label cannot be used in the transaction
|
|
31
|
+
* that adds it) runs alone, with `SET` for the session first; its
|
|
32
|
+
* `succeeded` row is written right after it, in a transaction of its own.
|
|
33
|
+
* A runner killed between the two leaves the statement run and its latest
|
|
34
|
+
* row `started`, and the next run sends it again.
|
|
35
|
+
*
|
|
36
|
+
* ## A failed CONCURRENTLY build
|
|
37
|
+
*
|
|
38
|
+
* `CREATE INDEX CONCURRENTLY` that fails (a lock timeout, a duplicate on a
|
|
39
|
+
* unique index, a cancel) leaves the index behind, INVALID: it is in the
|
|
40
|
+
* catalog, is maintained on every write, and is never used, and the next
|
|
41
|
+
* `CREATE INDEX` of that name fails because it exists. So before every
|
|
42
|
+
* attempt, the first, a retry, or a resume in a later run, and after every
|
|
43
|
+
* failure, an INVALID index of that name is dropped (`DROP INDEX
|
|
44
|
+
* CONCURRENTLY IF EXISTS`), and only an INVALID one: a valid index of that
|
|
45
|
+
* name is never touched.
|
|
46
|
+
*/
|
|
47
|
+
import { YodelError } from "../../project.js";
|
|
48
|
+
import { pgIdent } from "../../history/postgres/index.js";
|
|
49
|
+
/** SQLSTATE lock_not_available: the lock timeout passed. */
|
|
50
|
+
export const LOCK_NOT_AVAILABLE = "55P03";
|
|
51
|
+
export const DEFAULT_PG_TIMEOUTS = { lockTimeoutMs: 5_000, statementTimeoutMs: 60_000, scanTimeoutMs: 0 };
|
|
52
|
+
export const DEFAULT_LOCK_RETRIES = 5;
|
|
53
|
+
export const DEFAULT_RETRY_BASE_MS = 250;
|
|
54
|
+
/** A statement failed; `error` is the server's message. */
|
|
55
|
+
export class PgStatementError extends YodelError {
|
|
56
|
+
error;
|
|
57
|
+
code;
|
|
58
|
+
attempts;
|
|
59
|
+
constructor(error, code, attempts) {
|
|
60
|
+
super(error, 1);
|
|
61
|
+
this.error = error;
|
|
62
|
+
this.code = code;
|
|
63
|
+
this.attempts = attempts;
|
|
64
|
+
this.name = "PgStatementError";
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const sqlstate = (e) => e && typeof e === "object" && "code" in e && typeof e.code === "string" ? e.code : undefined;
|
|
68
|
+
const messageOf = (e) => (e instanceof Error ? e.message : String(e));
|
|
69
|
+
const ms = (n) => `'${String(Math.max(0, Math.floor(n)))}ms'`;
|
|
70
|
+
/** Whether a statement of `cls` reads or rewrites rows (chant's `scansRows`), so it runs with the scan timeout. */
|
|
71
|
+
export const scansRows = (cls) => cls === "rewrite" || cls === "validate" || cls === "concurrently";
|
|
72
|
+
const IDENT = String.raw `(?:"(?:[^"]|"")+"|[A-Za-z_][A-Za-z0-9_$]*)`;
|
|
73
|
+
const CONCURRENT_INDEX = new RegExp(String.raw `^\s*CREATE\s+(?:UNIQUE\s+)?INDEX\s+CONCURRENTLY\s+(?:IF\s+NOT\s+EXISTS\s+)?(${IDENT})\s+ON\s+(?:ONLY\s+)?(?:(${IDENT})\s*\.\s*)?${IDENT}`, "i");
|
|
74
|
+
const unquote = (id) => (id.startsWith('"') ? id.slice(1, -1).replace(/""/g, '"') : id.toLowerCase());
|
|
75
|
+
/** The index a `CREATE INDEX CONCURRENTLY` builds, `schema.name` quoted, when the statement names its table's schema. */
|
|
76
|
+
export function concurrentIndex(sql) {
|
|
77
|
+
const m = CONCURRENT_INDEX.exec(sql);
|
|
78
|
+
if (!m?.[1] || !m[2])
|
|
79
|
+
return undefined;
|
|
80
|
+
return `${pgIdent(unquote(m[2]))}.${pgIdent(unquote(m[1]))}`;
|
|
81
|
+
}
|
|
82
|
+
/** Drops `index` when it is there and INVALID. Returns whether it dropped it. */
|
|
83
|
+
export async function dropInvalidIndex(client, index, timeouts, log) {
|
|
84
|
+
const [row] = await client.query("SELECT NOT i.indisvalid AS invalid FROM pg_catalog.pg_index i WHERE i.indexrelid = pg_catalog.to_regclass($1)", [index]);
|
|
85
|
+
if (row?.invalid !== true)
|
|
86
|
+
return false;
|
|
87
|
+
log(` ${index} is INVALID, left by a CONCURRENTLY build that failed; dropping it before the build`);
|
|
88
|
+
await client.query(`SET lock_timeout = ${ms(timeouts.lockTimeoutMs)}`);
|
|
89
|
+
await client.query(`DROP INDEX CONCURRENTLY IF EXISTS ${index}`);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
const defaultSleep = (n) => new Promise((r) => setTimeout(r, n));
|
|
93
|
+
/** Runs one statement to success or failure; see the module comment. */
|
|
94
|
+
export async function runPgStatement(client, step, options) {
|
|
95
|
+
const { timeouts, log } = options;
|
|
96
|
+
const sleep = options.sleep ?? defaultSleep;
|
|
97
|
+
const base = options.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
98
|
+
const statementTimeout = scansRows(step.class) ? timeouts.scanTimeoutMs : timeouts.statementTimeoutMs;
|
|
99
|
+
const index = step.transactional ? undefined : concurrentIndex(step.sql);
|
|
100
|
+
const droppedInvalid = [];
|
|
101
|
+
const lockWaits = [];
|
|
102
|
+
const note = () => {
|
|
103
|
+
const parts = [];
|
|
104
|
+
if (lockWaits.length > 0)
|
|
105
|
+
parts.push(`lock_timeout ${String(timeouts.lockTimeoutMs)}ms hit ${String(lockWaits.length)} time(s); succeeded on attempt ${String(lockWaits.length + 1)}`);
|
|
106
|
+
if (droppedInvalid.length > 0)
|
|
107
|
+
parts.push(`dropped INVALID index ${droppedInvalid.join(", ")} before building it again`);
|
|
108
|
+
return parts.join("; ");
|
|
109
|
+
};
|
|
110
|
+
const cleanIndex = async () => {
|
|
111
|
+
if (index === undefined)
|
|
112
|
+
return;
|
|
113
|
+
try {
|
|
114
|
+
if (await dropInvalidIndex(client, index, timeouts, log))
|
|
115
|
+
droppedInvalid.push(index);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
log(` could not drop the INVALID index ${index}: ${messageOf(e)}; the next run tries again`);
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
for (let attempt = 1;; attempt++) {
|
|
122
|
+
try {
|
|
123
|
+
if (step.transactional) {
|
|
124
|
+
await client.query("BEGIN");
|
|
125
|
+
try {
|
|
126
|
+
await client.query(`SET LOCAL lock_timeout = ${ms(timeouts.lockTimeoutMs)}`);
|
|
127
|
+
await client.query(`SET LOCAL statement_timeout = ${ms(statementTimeout)}`);
|
|
128
|
+
await client.query(step.sql);
|
|
129
|
+
await options.succeeded(note());
|
|
130
|
+
await client.query("COMMIT");
|
|
131
|
+
}
|
|
132
|
+
catch (e) {
|
|
133
|
+
await client.query("ROLLBACK").catch(() => undefined);
|
|
134
|
+
throw e;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
else {
|
|
138
|
+
await cleanIndex();
|
|
139
|
+
try {
|
|
140
|
+
await client.query(`SET lock_timeout = ${ms(timeouts.lockTimeoutMs)}`);
|
|
141
|
+
await client.query(`SET statement_timeout = ${ms(statementTimeout)}`);
|
|
142
|
+
await client.query(step.sql);
|
|
143
|
+
}
|
|
144
|
+
catch (e) {
|
|
145
|
+
await cleanIndex();
|
|
146
|
+
throw e;
|
|
147
|
+
}
|
|
148
|
+
finally {
|
|
149
|
+
await client.query("SET lock_timeout TO DEFAULT").catch(() => undefined);
|
|
150
|
+
await client.query("SET statement_timeout TO DEFAULT").catch(() => undefined);
|
|
151
|
+
}
|
|
152
|
+
await options.succeeded(note());
|
|
153
|
+
}
|
|
154
|
+
return { attempts: attempt, droppedInvalid, note: note() };
|
|
155
|
+
}
|
|
156
|
+
catch (e) {
|
|
157
|
+
const code = sqlstate(e);
|
|
158
|
+
if (code === LOCK_NOT_AVAILABLE && attempt <= options.retries) {
|
|
159
|
+
lockWaits.push(attempt);
|
|
160
|
+
const wait = base * 2 ** (attempt - 1);
|
|
161
|
+
log(` lock_timeout ${String(timeouts.lockTimeoutMs)}ms: another session holds a lock this statement needs; trying again in ${String(wait)}ms (${String(attempt)}/${String(options.retries)})`);
|
|
162
|
+
await sleep(wait);
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
const message = messageOf(e);
|
|
166
|
+
throw new PgStatementError(code === LOCK_NOT_AVAILABLE
|
|
167
|
+
? `${message} (lock_timeout ${String(timeouts.lockTimeoutMs)}ms, tried ${String(attempt)} times: another session kept a lock this statement needs; SQLSTATE 55P03)`
|
|
168
|
+
: message, code, attempt);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"executor.js","sourceRoot":"","sources":["../../../src/versioned/postgres/executor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The versioned path on Postgres (#13): the driver the engine runs, and its
|
|
3
|
+
* parts.
|
|
4
|
+
*
|
|
5
|
+
* openPostgres(project, env) the server, the history store, the driver
|
|
6
|
+
* runPgStatement(client, step) one statement under lock_timeout, retried, with its row
|
|
7
|
+
* readPgCatalog / pgCatalogFingerprint the live schema the plan digest covers
|
|
8
|
+
*/
|
|
9
|
+
export * from "./catalog.js";
|
|
10
|
+
export * from "./driver.js";
|
|
11
|
+
export * from "./executor.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The versioned path on Postgres (#13): the driver the engine runs, and its
|
|
3
|
+
* parts.
|
|
4
|
+
*
|
|
5
|
+
* openPostgres(project, env) the server, the history store, the driver
|
|
6
|
+
* runPgStatement(client, step) one statement under lock_timeout, retried, with its row
|
|
7
|
+
* readPgCatalog / pgCatalogFingerprint the live schema the plan digest covers
|
|
8
|
+
*/
|
|
9
|
+
export * from "./catalog.js";
|
|
10
|
+
export * from "./driver.js";
|
|
11
|
+
export * from "./executor.js";
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/versioned/postgres/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,eAAe,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads of the live schema that another session's lock can block (#18): the
|
|
3
|
+
* fingerprint the plan digest covers, read before every apply and plan.
|
|
4
|
+
* chant's catalog reader calls `pg_get_viewdef()` and friends, which wait
|
|
5
|
+
* for a table's ACCESS SHARE lock, so a session holding ACCESS EXCLUSIVE on
|
|
6
|
+
* a declared table (a long `ALTER TABLE`, a `LOCK TABLE`) would hold the read
|
|
7
|
+
* up for as long as it holds the lock, with nothing printed.
|
|
8
|
+
*
|
|
9
|
+
* So the read runs under the profile's `lockTimeoutMs`, and when that passes
|
|
10
|
+
* (SQLSTATE 55P03) yodel says which lock it waited on and who holds it, and
|
|
11
|
+
* tries again with the same pauses and count as a statement (250 ms, then
|
|
12
|
+
* doubling, YODEL_LOCK_RETRIES more times). When the lock is still held after
|
|
13
|
+
* the last try, the read fails with the same description.
|
|
14
|
+
*/
|
|
15
|
+
import type { PgClient } from "../../history/postgres/index.js";
|
|
16
|
+
/** A session's ACCESS EXCLUSIVE lock on a relation, held or queued for. */
|
|
17
|
+
export interface LockHolder {
|
|
18
|
+
schema: string;
|
|
19
|
+
name: string;
|
|
20
|
+
pid: number;
|
|
21
|
+
granted: boolean;
|
|
22
|
+
application: string;
|
|
23
|
+
}
|
|
24
|
+
/** The ACCESS EXCLUSIVE locks other sessions hold (or wait for) on relations in `schemas` (every schema but the server's own when undefined). */
|
|
25
|
+
export declare function lockHolders(client: PgClient, schemas: readonly string[] | undefined): Promise<LockHolder[]>;
|
|
26
|
+
/** `a lock on shop.orders (held by pid 123, psql)`, or `a lock held by another session` when none could be named. */
|
|
27
|
+
export declare function describeHolders(holders: readonly LockHolder[]): string;
|
|
28
|
+
export interface LockedReadOptions {
|
|
29
|
+
lockTimeoutMs: number;
|
|
30
|
+
retries: number;
|
|
31
|
+
/** The schemas to look for the lock in. */
|
|
32
|
+
schemas: readonly string[] | undefined;
|
|
33
|
+
/** What is being read, for the messages: `the live schema`. */
|
|
34
|
+
what: string;
|
|
35
|
+
log: (line: string) => void;
|
|
36
|
+
retryBaseMs?: number;
|
|
37
|
+
sleep?: (ms: number) => Promise<void>;
|
|
38
|
+
}
|
|
39
|
+
declare const seconds: (ms: number) => string;
|
|
40
|
+
/** Runs `read` on `client` with `lock_timeout` set, retrying (and saying why) when another session's lock holds it up. */
|
|
41
|
+
export declare function readUnderLockTimeout<T>(client: PgClient, read: () => Promise<T>, o: LockedReadOptions): Promise<T>;
|
|
42
|
+
/** The `PGOPTIONS` a chant child reads Postgres under, so a read blocked by a lock gives up after `lockTimeoutMs` (node-postgres passes it to the server). */
|
|
43
|
+
export declare function lockTimeoutOptions(lockTimeoutMs: number, existing: string | undefined): string;
|
|
44
|
+
/** Whether a read failed because the lock timeout passed. */
|
|
45
|
+
export declare const isLockTimeout: (text: string | undefined) => boolean;
|
|
46
|
+
export { seconds as describeMs };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads of the live schema that another session's lock can block (#18): the
|
|
3
|
+
* fingerprint the plan digest covers, read before every apply and plan.
|
|
4
|
+
* chant's catalog reader calls `pg_get_viewdef()` and friends, which wait
|
|
5
|
+
* for a table's ACCESS SHARE lock, so a session holding ACCESS EXCLUSIVE on
|
|
6
|
+
* a declared table (a long `ALTER TABLE`, a `LOCK TABLE`) would hold the read
|
|
7
|
+
* up for as long as it holds the lock, with nothing printed.
|
|
8
|
+
*
|
|
9
|
+
* So the read runs under the profile's `lockTimeoutMs`, and when that passes
|
|
10
|
+
* (SQLSTATE 55P03) yodel says which lock it waited on and who holds it, and
|
|
11
|
+
* tries again with the same pauses and count as a statement (250 ms, then
|
|
12
|
+
* doubling, YODEL_LOCK_RETRIES more times). When the lock is still held after
|
|
13
|
+
* the last try, the read fails with the same description.
|
|
14
|
+
*/
|
|
15
|
+
import { YodelError } from "../../project.js";
|
|
16
|
+
import { DEFAULT_RETRY_BASE_MS, LOCK_NOT_AVAILABLE } from "./executor.js";
|
|
17
|
+
const sqlstate = (e) => e && typeof e === "object" && "code" in e && typeof e.code === "string" ? e.code : undefined;
|
|
18
|
+
/** The ACCESS EXCLUSIVE locks other sessions hold (or wait for) on relations in `schemas` (every schema but the server's own when undefined). */
|
|
19
|
+
export async function lockHolders(client, schemas) {
|
|
20
|
+
const rows = await client.query(`SELECT n.nspname AS schema, c.relname AS name, l.pid, l.granted, a.application_name AS application
|
|
21
|
+
FROM pg_catalog.pg_locks l
|
|
22
|
+
JOIN pg_catalog.pg_class c ON c.oid = l.relation
|
|
23
|
+
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
|
|
24
|
+
LEFT JOIN pg_catalog.pg_stat_activity a ON a.pid = l.pid
|
|
25
|
+
WHERE l.locktype = 'relation' AND l.mode = 'AccessExclusiveLock' AND l.pid <> pg_catalog.pg_backend_pid()
|
|
26
|
+
AND n.nspname NOT IN ('pg_catalog', 'information_schema') AND n.nspname NOT LIKE 'pg\\_toast%'
|
|
27
|
+
AND ($1::text[] IS NULL OR n.nspname = ANY ($1::text[]))
|
|
28
|
+
ORDER BY l.granted DESC, n.nspname, c.relname, l.pid
|
|
29
|
+
LIMIT 5`, [schemas ? [...schemas] : null]);
|
|
30
|
+
return rows.map((r) => ({ schema: r.schema, name: r.name, pid: Number(r.pid), granted: r.granted, application: r.application ?? "" }));
|
|
31
|
+
}
|
|
32
|
+
/** `a lock on shop.orders (held by pid 123, psql)`, or `a lock held by another session` when none could be named. */
|
|
33
|
+
export function describeHolders(holders) {
|
|
34
|
+
if (holders.length === 0)
|
|
35
|
+
return "a lock another session holds on an object in scope";
|
|
36
|
+
const one = (h) => `${h.schema}.${h.name} (${h.granted ? "held" : "queued for"} by pid ${String(h.pid)}${h.application ? `, ${h.application}` : ""})`;
|
|
37
|
+
return `a lock on ${holders.map(one).join(", ")}`;
|
|
38
|
+
}
|
|
39
|
+
const sleeper = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
40
|
+
const seconds = (ms) => (ms < 1000 ? `${String(ms)}ms` : `${String(Math.round(ms / 100) / 10)}s`);
|
|
41
|
+
/** Runs `read` on `client` with `lock_timeout` set, retrying (and saying why) when another session's lock holds it up. */
|
|
42
|
+
export async function readUnderLockTimeout(client, read, o) {
|
|
43
|
+
const sleep = o.sleep ?? sleeper;
|
|
44
|
+
const base = o.retryBaseMs ?? DEFAULT_RETRY_BASE_MS;
|
|
45
|
+
const [prev] = await client.query("SELECT pg_catalog.current_setting('lock_timeout') AS lock_timeout");
|
|
46
|
+
await client.query("SELECT pg_catalog.set_config('lock_timeout', $1, false)", [`${String(Math.max(0, Math.floor(o.lockTimeoutMs)))}ms`]);
|
|
47
|
+
try {
|
|
48
|
+
for (let attempt = 0;; attempt++) {
|
|
49
|
+
try {
|
|
50
|
+
return await read();
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
if (sqlstate(e) !== LOCK_NOT_AVAILABLE)
|
|
54
|
+
throw e;
|
|
55
|
+
const holders = await lockHolders(client, o.schemas).catch(() => []);
|
|
56
|
+
const waited = `reading ${o.what} waited ${seconds(o.lockTimeoutMs)} for ${describeHolders(holders)}`;
|
|
57
|
+
if (attempt >= o.retries) {
|
|
58
|
+
const pid = holders.find((h) => h.granted)?.pid;
|
|
59
|
+
throw new YodelError(`${waited}, ${String(attempt + 1)} time(s); nothing was changed. Try again once it is released${pid !== undefined ? `, or end that session with: SELECT pg_terminate_backend(${String(pid)})` : ""}`);
|
|
60
|
+
}
|
|
61
|
+
const pause = base * 2 ** attempt;
|
|
62
|
+
o.log(`${waited}; trying again in ${seconds(pause)} (${String(attempt + 1)} of ${String(o.retries)})`);
|
|
63
|
+
await sleep(pause);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
finally {
|
|
68
|
+
await client.query("SELECT pg_catalog.set_config('lock_timeout', $1, false)", [prev?.lock_timeout ?? "0"]).catch(() => undefined);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
/** The `PGOPTIONS` a chant child reads Postgres under, so a read blocked by a lock gives up after `lockTimeoutMs` (node-postgres passes it to the server). */
|
|
72
|
+
export function lockTimeoutOptions(lockTimeoutMs, existing) {
|
|
73
|
+
return [existing?.trim(), `-c lock_timeout=${String(Math.max(0, Math.floor(lockTimeoutMs)))}`].filter(Boolean).join(" ");
|
|
74
|
+
}
|
|
75
|
+
/** Whether a read failed because the lock timeout passed. */
|
|
76
|
+
export const isLockTimeout = (text) => text !== undefined && /lock timeout/i.test(text);
|
|
77
|
+
export { seconds as describeMs };
|
|
78
|
+
//# sourceMappingURL=waits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"waits.js","sourceRoot":"","sources":["../../../src/versioned/postgres/waits.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** The versioned status as text and as JSON, for `yodel status` and `yodel apply --plan`. */
|
|
2
|
+
import type { SqlDialect } from "../project.js";
|
|
3
|
+
import { type HistoryLocation } from "../history/index.js";
|
|
4
|
+
import { type ServerInfo } from "../server.js";
|
|
5
|
+
import { type VersionedStatus } from "./state.js";
|
|
6
|
+
export interface StatusReport {
|
|
7
|
+
environment: string;
|
|
8
|
+
/** The server the history was read from (#11). */
|
|
9
|
+
server?: ServerInfo | undefined;
|
|
10
|
+
/** Postgres has no topology: its report leaves it out. Default ClickHouse. */
|
|
11
|
+
dialect?: SqlDialect | undefined;
|
|
12
|
+
location: HistoryLocation;
|
|
13
|
+
/** The topology statements are rendered for, and where it came from: `cluster:main (yodel.config.ts)`. Default: the history's. */
|
|
14
|
+
topology?: string | undefined;
|
|
15
|
+
status: VersionedStatus;
|
|
16
|
+
/** The digest `yodel apply` would ask approval for, when it would not refuse. */
|
|
17
|
+
digest?: string | undefined;
|
|
18
|
+
/** Why `yodel apply` would refuse, with its exit code. */
|
|
19
|
+
refusal?: {
|
|
20
|
+
exitCode: number;
|
|
21
|
+
message: string;
|
|
22
|
+
} | undefined;
|
|
23
|
+
}
|
|
24
|
+
export declare function statusJson(r: StatusReport): Record<string, unknown>;
|
|
25
|
+
export declare function renderStatus(r: StatusReport): string;
|