okmodel 0.3.0 → 0.4.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/README.md +182 -22
- package/dist/contracts/catalog/document.d.ts +6 -2
- package/dist/dialects/pg/ext/citext.js +1 -1
- package/dist/dialects/pg/ext/index.js +1 -1
- package/dist/dialects/pg/ext/pg-trgm.js +1 -1
- package/dist/dialects/pg/index.js +3 -3
- package/dist/dialects/pg/role/sql.d.ts +11 -4
- package/dist/dialects/pg/table.d.ts +17 -1
- package/dist/dialects/pg/view/index.js +1 -1
- package/dist/dialects/pg/view/scratch.d.ts +3 -2
- package/dist/runtime/catalog-cache.d.ts +17 -0
- package/dist/runtime/compat-words.d.ts +23 -0
- package/dist/runtime/drift-detail.d.ts +20 -4
- package/dist/runtime/drift.d.ts +4 -2
- package/dist/runtime/pg/bun.js +2 -2
- package/dist/runtime/pg/pg.js +2 -2
- package/dist/runtime/pg/pglite.js +2 -2
- package/dist/runtime/pg/postgresjs.js +2 -2
- package/dist/runtime/traits/index.js +5 -4
- package/dist/runtime/types.d.ts +2 -0
- package/dist/runtime/validate/index.js +2 -2
- package/dist/shared/2321avmn.js +418 -0
- package/dist/shared/{x6wysh83.js → 3p4b1fg0.js} +3 -5
- package/dist/shared/{wekze34d.js → 41kv2xdm.js} +74 -2
- package/dist/shared/{fs0ex00p.js → 4ef7mmqf.js} +2 -2
- package/dist/shared/5mtf6cj3.js +94 -0
- package/dist/shared/{999ade2q.js → 5vdm4sgp.js} +1 -1
- package/dist/shared/{qybh48nd.js → 6693j9rs.js} +1 -1
- package/dist/shared/{bqha5cg6.js → 6p5k20sm.js} +2 -2
- package/dist/shared/7nk68fj8.js +751 -0
- package/dist/shared/{ne02ng5c.js → 84ptn35q.js} +7 -4
- package/dist/shared/{t616ynfn.js → 8jb4ymh2.js} +59 -12
- package/dist/shared/{th14ygv2.js → 8ktwd3rh.js} +29 -1
- package/dist/shared/9yn6a2vd.js +11 -0
- package/dist/shared/ady1ms74.js +72 -0
- package/dist/shared/{scwxrpx2.js → c5g81ngb.js} +82 -15
- package/dist/shared/{6bazvqwm.js → fb00y8fp.js} +2 -2
- package/dist/shared/{jkc6ws1p.js → g7ez901n.js} +83 -2
- package/dist/shared/{kgnc8grt.js → gdh9mrtp.js} +1 -1
- package/dist/shared/hqjsxnz7.js +13 -0
- package/dist/shared/htnaj77v.js +17 -0
- package/dist/shared/{mt5wswjz.js → jgxzjzcz.js} +1 -1
- package/dist/shared/{6w5j844a.js → jsd07prz.js} +2 -2
- package/dist/shared/{m78s7898.js → kafaa5zv.js} +1 -1
- package/dist/shared/kj680nxv.js +19 -0
- package/dist/shared/{k34e0xf2.js → kkzwp3d2.js} +8 -8
- package/dist/shared/{2ssqrv66.js → n5y9zyj1.js} +1029 -174
- package/dist/shared/{ty3v3hfk.js → nxb42q7h.js} +2 -2
- package/dist/shared/{5nmdenb6.js → q6zsyh31.js} +1003 -40
- package/dist/shared/{fptpmvxp.js → q795afwa.js} +26 -21
- package/dist/shared/{3ekj9x92.js → qn0srwwq.js} +1 -1
- package/dist/shared/s4mb6a4b.js +545 -0
- package/dist/shared/{01yr20s5.js → sf8m6tfy.js} +2 -2
- package/dist/shared/{bryqk9k9.js → sr5ms7b3.js} +1 -1
- package/dist/shared/{meck53jv.js → tny2t3sm.js} +6 -5
- package/dist/shared/{p24x2334.js → v7tpnhnz.js} +1 -1
- package/dist/shared/{xw1d69vd.js → wqem4jed.js} +6 -6
- package/dist/shared/{49ypnfwt.js → xghkt7a9.js} +50 -28
- package/dist/shared/{7fvtrkwr.js → ycatkxmq.js} +1 -1
- package/dist/shared/ygem90rn.js +900 -0
- package/dist/shared/{1tfdfvhh.js → yjw02enp.js} +6 -4
- package/dist/shared/{ydaz1sv3.js → zg24x0hm.js} +1 -1
- package/dist/tooling/cli.js +46 -17
- package/dist/tooling/migrate/apply.d.ts +59 -5
- package/dist/tooling/migrate/backfill.d.ts +136 -0
- package/dist/tooling/migrate/check.d.ts +65 -0
- package/dist/tooling/migrate/classify.d.ts +108 -0
- package/dist/tooling/migrate/commands.d.ts +2 -2
- package/dist/tooling/migrate/config.d.ts +22 -0
- package/dist/tooling/migrate/doctor.d.ts +3 -0
- package/dist/tooling/migrate/drift.d.ts +18 -2
- package/dist/tooling/migrate/estimate.d.ts +50 -0
- package/dist/tooling/migrate/extensions.d.ts +2 -0
- package/dist/tooling/migrate/files.d.ts +12 -0
- package/dist/tooling/migrate/index.d.ts +3 -2
- package/dist/tooling/migrate/index.js +67 -1
- package/dist/tooling/migrate/lint-rules.d.ts +51 -0
- package/dist/tooling/migrate/lint.d.ts +71 -0
- package/dist/tooling/migrate/managed.d.ts +1 -1
- package/dist/tooling/migrate/plan.d.ts +64 -9
- package/dist/tooling/migrate/project.d.ts +42 -7
- package/dist/tooling/migrate/provision.d.ts +18 -0
- package/dist/tooling/migrate/reference.d.ts +40 -0
- package/dist/tooling/migrate/runner.d.ts +108 -0
- package/dist/tooling/migrate/safe.d.ts +77 -0
- package/dist/tooling/migrate/seed.d.ts +20 -0
- package/dist/tooling/migrate/snapshot.d.ts +78 -0
- package/dist/tooling/migrate/status.d.ts +25 -6
- package/dist/tooling/testing/factories.d.ts +117 -0
- package/dist/tooling/testing/facts.d.ts +72 -0
- package/dist/tooling/testing/index.d.ts +89 -0
- package/dist/tooling/testing/index.js +8 -0
- package/dist/tooling/testing/isolation.d.ts +30 -0
- package/dist/tooling/testing/random.d.ts +40 -0
- package/dist/tooling/testing/record.d.ts +38 -0
- package/package.json +6 -2
- package/dist/shared/dbjej1tw.js +0 -456
- package/dist/shared/g4f5kcx9.js +0 -37
- package/dist/shared/qbxzvwwc.js +0 -88
- package/dist/shared/zwmqxb0g.js +0 -79
- package/dist/shared/{xva8v7qk.js → awrsqrn7.js} +3 -3
- package/dist/shared/{75xkjv0t.js → fa9vt245.js} +0 -0
- package/dist/shared/{m9tv7xn7.js → radd3z4g.js} +3 -3
- /package/dist/shared/{0jc3ffzf.js → zvbjc949.js} +0 -0
|
@@ -7,27 +7,40 @@ async function assertCompatible(pool, schema, recordedHash, source, options) {
|
|
|
7
7
|
const code = await codeHash(schema, source);
|
|
8
8
|
if (code.hash === recordedHash)
|
|
9
9
|
return;
|
|
10
|
+
const { loadOnce } = await import("./htnaj77v.js");
|
|
10
11
|
if (code.artifact !== undefined) {
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const artifact = await readCatalogFile(source.catalogDir);
|
|
15
|
-
if (artifact !== undefined) {
|
|
16
|
-
const { loadTrustedCatalog } = await import("./qybh48nd.js");
|
|
12
|
+
const artifact = code.artifact;
|
|
13
|
+
await loadOnce(`text\x00${artifact.hash}`, async () => {
|
|
14
|
+
const { loadTrustedCatalog } = await import("./6693j9rs.js");
|
|
17
15
|
loadTrustedCatalog(artifact.text, artifact.hash);
|
|
18
|
-
}
|
|
16
|
+
});
|
|
17
|
+
} else if (code.fromFile) {
|
|
18
|
+
const dir = artifactDir(source.catalogDir);
|
|
19
|
+
await loadOnce(`file\x00${dir}\x00${code.hash}`, async () => {
|
|
20
|
+
const text = await readText(`${dir}/catalog.json`);
|
|
21
|
+
if (text === undefined) {
|
|
22
|
+
throw new OkmError2("OKM1027", `${dir}/catalog.json is missing.`, {
|
|
23
|
+
fix: {
|
|
24
|
+
summary: "Run okm build so catalog.json matches catalog.hash. connect does not rebuild the catalog from the schema."
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
const { loadTrustedCatalog } = await import("./6693j9rs.js");
|
|
29
|
+
loadTrustedCatalog(text, code.hash);
|
|
30
|
+
});
|
|
19
31
|
}
|
|
20
|
-
const { assertDrift } = await import("./
|
|
32
|
+
const { assertDrift } = await import("./5mtf6cj3.js");
|
|
21
33
|
await assertDrift(pool, code.hash, recordedHash, options);
|
|
22
34
|
}
|
|
23
35
|
async function codeHash(schema, source) {
|
|
24
|
-
if (source.catalog !== undefined)
|
|
25
|
-
return { hash: source.catalog.hash, artifact: source.catalog };
|
|
36
|
+
if (source.catalog !== undefined) {
|
|
37
|
+
return { hash: source.catalog.hash, artifact: source.catalog, fromFile: false };
|
|
38
|
+
}
|
|
26
39
|
const listed = await readHashFile(source.catalogDir);
|
|
27
40
|
if (listed !== undefined)
|
|
28
|
-
return { hash: listed };
|
|
29
|
-
const { catalogHash } = await import("./
|
|
30
|
-
return { hash: catalogHash(schemaCatalog(schema)) };
|
|
41
|
+
return { hash: listed, fromFile: true };
|
|
42
|
+
const { catalogHash } = await import("./6693j9rs.js");
|
|
43
|
+
return { hash: catalogHash(schemaCatalog(schema)), fromFile: false };
|
|
31
44
|
}
|
|
32
45
|
function schemaCatalog(schema) {
|
|
33
46
|
const value = Reflect.get(schema, "catalog");
|
|
@@ -47,14 +60,6 @@ async function readHashFile(catalogDir) {
|
|
|
47
60
|
const hash = text.trim();
|
|
48
61
|
return hash.length === 0 ? undefined : hash;
|
|
49
62
|
}
|
|
50
|
-
async function readCatalogFile(catalogDir) {
|
|
51
|
-
const dir = artifactDir(catalogDir);
|
|
52
|
-
const hash = await readHashFile(catalogDir);
|
|
53
|
-
const text = await readText(`${dir}/catalog.json`);
|
|
54
|
-
if (hash === undefined || text === undefined)
|
|
55
|
-
return;
|
|
56
|
-
return { text, hash };
|
|
57
|
-
}
|
|
58
63
|
function artifactDir(catalogDir) {
|
|
59
64
|
if (catalogDir !== undefined)
|
|
60
65
|
return catalogDir.replace(/\/$/, "");
|
|
@@ -0,0 +1,545 @@
|
|
|
1
|
+
import {
|
|
2
|
+
open2
|
|
3
|
+
} from "./wqem4jed.js";
|
|
4
|
+
import {
|
|
5
|
+
catalogHash2
|
|
6
|
+
} from "./8jb4ymh2.js";
|
|
7
|
+
import {
|
|
8
|
+
runTarget,
|
|
9
|
+
assumeRole,
|
|
10
|
+
statementOutsideTransaction,
|
|
11
|
+
loadMigrations,
|
|
12
|
+
loadHeadSnapshot,
|
|
13
|
+
readReference,
|
|
14
|
+
referenceInserts,
|
|
15
|
+
provisionMark,
|
|
16
|
+
expandProvisioned,
|
|
17
|
+
installSnapshot
|
|
18
|
+
} from "./7nk68fj8.js";
|
|
19
|
+
import {
|
|
20
|
+
assertCreateRole,
|
|
21
|
+
currentUser,
|
|
22
|
+
roleExists
|
|
23
|
+
} from "./hcmdtwgh.js";
|
|
24
|
+
import {
|
|
25
|
+
lintMigrationDirectory,
|
|
26
|
+
hasError,
|
|
27
|
+
lintRefusal,
|
|
28
|
+
assertTargetPolicy,
|
|
29
|
+
assertDirectConnection,
|
|
30
|
+
selectTarget,
|
|
31
|
+
projectHead,
|
|
32
|
+
loadBuiltSchema,
|
|
33
|
+
loadConfig
|
|
34
|
+
} from "./q6zsyh31.js";
|
|
35
|
+
import {
|
|
36
|
+
changesRole,
|
|
37
|
+
batchSizeField,
|
|
38
|
+
assertExtensionsAvailable,
|
|
39
|
+
planMigration
|
|
40
|
+
} from "./n5y9zyj1.js";
|
|
41
|
+
import {
|
|
42
|
+
OkmError2
|
|
43
|
+
} from "./52szzakm.js";
|
|
44
|
+
|
|
45
|
+
// src/tooling/migrate/apply.ts
|
|
46
|
+
import { join } from "node:path";
|
|
47
|
+
|
|
48
|
+
// src/tooling/migrate/engine.ts
|
|
49
|
+
var IDENT = String.raw`"(?:[^"]|"")+"|[A-Za-z_][\w$]*`;
|
|
50
|
+
var DEFAULT_UUIDV7 = /\bdefault\s+uuidv7\s*\(\s*\)/i;
|
|
51
|
+
var TABLE = new RegExp(String.raw`^\s*(?:create\s+table(?:\s+if\s+not\s+exists)?|alter\s+table(?:\s+if\s+exists)?(?:\s+only)?)\s+(?:(?:${IDENT})\.)?(${IDENT})`, "i");
|
|
52
|
+
var COLUMN = new RegExp(String.raw`(${IDENT})\s+(?:uuid\b|set\s+default\b)`, "i");
|
|
53
|
+
function findUuidV7Default(migrations) {
|
|
54
|
+
for (const migration of migrations) {
|
|
55
|
+
for (let stepIndex = 0;stepIndex < migration.steps.length; stepIndex += 1) {
|
|
56
|
+
const sql = migration.steps[stepIndex]?.sql ?? "";
|
|
57
|
+
if (!DEFAULT_UUIDV7.test(sql))
|
|
58
|
+
continue;
|
|
59
|
+
return { migrationId: migration.id, stepIndex, column: columnOf(sql) };
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
async function assertUuidV7Available(connection, migrations) {
|
|
65
|
+
const found = findUuidV7Default(migrations);
|
|
66
|
+
if (found === undefined)
|
|
67
|
+
return;
|
|
68
|
+
const result = await connection.execute("select current_setting('server_version_num'), to_regprocedure('uuidv7()')::text");
|
|
69
|
+
const row = result.rows[0];
|
|
70
|
+
const major = Math.floor(Number(row?.[0] ?? "0") / 1e4);
|
|
71
|
+
const defined = row?.[1] !== null && row?.[1] !== undefined;
|
|
72
|
+
if (major >= 18 || defined)
|
|
73
|
+
return;
|
|
74
|
+
const where = found.column === undefined ? "A column" : `Column ${found.column}`;
|
|
75
|
+
throw new OkmError2("OKM1812", `${where} has a uuidv7() default (migration ${found.migrationId}, step ${String(found.stepIndex)}), and the server is PostgreSQL ${String(major)}. uuidv7() arrives in PostgreSQL 18. Nothing was changed.`, {
|
|
76
|
+
fix: {
|
|
77
|
+
summary: 'Either run PostgreSQL 18 and declare schema({ requires: { postgres: ">=18" } }), or use t.id({ default: "uuidv4" }) (or schema({ defaults: { id: "uuidv4" } })), which uses gen_random_uuid() and works on PostgreSQL 13 and newer. Then delete the migration that was not applied and generate it again.'
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function columnOf(sql) {
|
|
82
|
+
const table = TABLE.exec(sql)?.[1];
|
|
83
|
+
if (table === undefined)
|
|
84
|
+
return;
|
|
85
|
+
const line = sql.split(`
|
|
86
|
+
`).find((text) => DEFAULT_UUIDV7.test(text));
|
|
87
|
+
const column = line === undefined ? undefined : COLUMN.exec(line)?.[1];
|
|
88
|
+
if (column === undefined)
|
|
89
|
+
return;
|
|
90
|
+
return `${unquote(table)}.${unquote(column)}`;
|
|
91
|
+
}
|
|
92
|
+
function unquote(name) {
|
|
93
|
+
return name.startsWith('"') ? name.slice(1, -1).replaceAll('""', '"') : name;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// src/tooling/migrate/apply.ts
|
|
97
|
+
var DEFAULT_LOCK_MS = 5000;
|
|
98
|
+
var DEFAULT_STATEMENT_MS = 30000;
|
|
99
|
+
var DEFAULT_RETRIES = 3;
|
|
100
|
+
var META = `create table if not exists okm_meta (
|
|
101
|
+
id text primary key,
|
|
102
|
+
catalog_hash text not null,
|
|
103
|
+
migration_id text not null
|
|
104
|
+
)`;
|
|
105
|
+
var HISTORY = `create table if not exists okm_history (
|
|
106
|
+
migration_id text not null,
|
|
107
|
+
step_index integer not null,
|
|
108
|
+
class text not null,
|
|
109
|
+
catalog_hash text not null,
|
|
110
|
+
applied_at timestamptz not null default now(),
|
|
111
|
+
primary key (migration_id, step_index)
|
|
112
|
+
)`;
|
|
113
|
+
var BACKFILL = `create table if not exists okm_backfill (
|
|
114
|
+
migration_id text not null,
|
|
115
|
+
step_index integer not null,
|
|
116
|
+
last_key text,
|
|
117
|
+
rows_touched bigint not null default 0,
|
|
118
|
+
batches integer not null default 0,
|
|
119
|
+
state text not null,
|
|
120
|
+
updated_at timestamptz not null default now(),
|
|
121
|
+
primary key (migration_id, step_index)
|
|
122
|
+
)`;
|
|
123
|
+
function applyUnits(migration, done) {
|
|
124
|
+
const pending = [];
|
|
125
|
+
for (let index = 0;index < migration.steps.length; index += 1) {
|
|
126
|
+
if (done.has(`${migration.id}:${String(index)}`))
|
|
127
|
+
continue;
|
|
128
|
+
const step = migration.steps[index];
|
|
129
|
+
if (step !== undefined)
|
|
130
|
+
pending.push({ index, step });
|
|
131
|
+
}
|
|
132
|
+
const units = [];
|
|
133
|
+
let batch = [];
|
|
134
|
+
const flush = () => {
|
|
135
|
+
if (batch.length === 0)
|
|
136
|
+
return;
|
|
137
|
+
const last = batch.at(-1);
|
|
138
|
+
units.push({
|
|
139
|
+
migrationId: migration.id,
|
|
140
|
+
catalogHash: migration.catalogHash,
|
|
141
|
+
finishes: last !== undefined && last.index === migration.steps.length - 1,
|
|
142
|
+
transactional: true,
|
|
143
|
+
steps: batch
|
|
144
|
+
});
|
|
145
|
+
batch = [];
|
|
146
|
+
};
|
|
147
|
+
for (const item of pending) {
|
|
148
|
+
if (item.step.backfill === undefined && item.step.transactional && !statementOutsideTransaction(item.step.sql)) {
|
|
149
|
+
batch.push(item);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
flush();
|
|
153
|
+
units.push({
|
|
154
|
+
migrationId: migration.id,
|
|
155
|
+
catalogHash: migration.catalogHash,
|
|
156
|
+
finishes: item.index === migration.steps.length - 1,
|
|
157
|
+
transactional: false,
|
|
158
|
+
steps: [item]
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
flush();
|
|
162
|
+
return units;
|
|
163
|
+
}
|
|
164
|
+
async function applyTarget2(request) {
|
|
165
|
+
assertDirectConnection(request.url, request.allowPooler === true);
|
|
166
|
+
const pool = open2({
|
|
167
|
+
url: request.url,
|
|
168
|
+
max: 1,
|
|
169
|
+
...request.searchPath !== undefined ? { searchPath: request.searchPath } : {}
|
|
170
|
+
});
|
|
171
|
+
if (pool.reserve === undefined) {
|
|
172
|
+
await pool.close();
|
|
173
|
+
throw new OkmError2("invalid", "The driver cannot reserve a connection.", {
|
|
174
|
+
fix: { summary: "Apply needs a driver with interactive transactions." }
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
const connection = await pool.reserve();
|
|
178
|
+
let locked = false;
|
|
179
|
+
try {
|
|
180
|
+
await setTimeouts(connection, request);
|
|
181
|
+
await lockTarget(connection, request.target);
|
|
182
|
+
locked = true;
|
|
183
|
+
request.onLocked?.();
|
|
184
|
+
if (request.lintDirectory !== undefined) {
|
|
185
|
+
await refusePendingLint(connection, request.lintDirectory, request.migrations);
|
|
186
|
+
}
|
|
187
|
+
const session = {};
|
|
188
|
+
await prepareMigrationRole(connection, request, session);
|
|
189
|
+
await assertExtensionsAvailable(connection, request.migrations);
|
|
190
|
+
await assertUuidV7Available(connection, request.migrations);
|
|
191
|
+
const state = request.snapshot === undefined ? undefined : await targetState(connection);
|
|
192
|
+
if (request.snapshot !== undefined && state !== undefined) {
|
|
193
|
+
if (state.kind === "empty") {
|
|
194
|
+
return await provisionEmpty(connection, request, session);
|
|
195
|
+
}
|
|
196
|
+
if (state.kind === "interrupted")
|
|
197
|
+
throw interruptedProvision(request.target, state.object);
|
|
198
|
+
if (state.kind === "occupied" || request.snapshot.only === true) {
|
|
199
|
+
throw notEmpty(request.target, state.kind === "occupied" ? state.object : "okm_meta");
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
await connection.execute(META);
|
|
203
|
+
await connection.execute(HISTORY);
|
|
204
|
+
await connection.execute(BACKFILL);
|
|
205
|
+
const history = await readHistory(connection);
|
|
206
|
+
const done = expandProvisioned(request.migrations, history.done, history.ids);
|
|
207
|
+
const units = request.migrations.flatMap((migration) => applyUnits(migration, done));
|
|
208
|
+
const report = await runTarget(connection, {
|
|
209
|
+
policy: request,
|
|
210
|
+
units,
|
|
211
|
+
retries: request.retries ?? DEFAULT_RETRIES,
|
|
212
|
+
lockTimeoutMs: request.lockTimeoutMs ?? DEFAULT_LOCK_MS,
|
|
213
|
+
statementTimeoutMs: request.statementTimeoutMs ?? DEFAULT_STATEMENT_MS,
|
|
214
|
+
backfillStatementTimeoutMs: request.backfillStatementTimeoutMs ?? request.statementTimeoutMs ?? DEFAULT_STATEMENT_MS,
|
|
215
|
+
...request.backfillPauseMs !== undefined ? { backfillPauseMs: request.backfillPauseMs } : {},
|
|
216
|
+
...request.migrationRole !== undefined ? { migrationRole: request.migrationRole } : {},
|
|
217
|
+
...request.schema !== undefined ? { schema: request.schema } : {},
|
|
218
|
+
...request.onProgress !== undefined ? { onProgress: request.onProgress } : {},
|
|
219
|
+
...request.afterBatch !== undefined ? { afterBatch: request.afterBatch } : {}
|
|
220
|
+
}, session);
|
|
221
|
+
if (request.snapshot !== undefined) {
|
|
222
|
+
await insertReference(connection, request);
|
|
223
|
+
}
|
|
224
|
+
return report;
|
|
225
|
+
} finally {
|
|
226
|
+
if (locked)
|
|
227
|
+
await connection.execute("select pg_advisory_unlock(hashtext($1))", [
|
|
228
|
+
lockKey(request.target)
|
|
229
|
+
]);
|
|
230
|
+
await connection.release();
|
|
231
|
+
await pool.close();
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function applyProject2(cwd, flags, write) {
|
|
235
|
+
const config = await loadConfig(cwd);
|
|
236
|
+
const target = selectTarget(config, flags.target);
|
|
237
|
+
const directory = joinMigrations(cwd, config.migrations);
|
|
238
|
+
const migrations = loadMigrations(directory);
|
|
239
|
+
const head = loadHeadSnapshot(directory);
|
|
240
|
+
if (migrations.length === 0 || head === undefined)
|
|
241
|
+
return `target ${target.name}
|
|
242
|
+
nothing to apply
|
|
243
|
+
`;
|
|
244
|
+
const built = await loadBuiltSchema(cwd, config).catch((error) => {
|
|
245
|
+
if (error instanceof OkmError2 && error.message.endsWith("must export a schema()."))
|
|
246
|
+
return;
|
|
247
|
+
throw error;
|
|
248
|
+
});
|
|
249
|
+
const progress = [];
|
|
250
|
+
const report = await applyTarget2({
|
|
251
|
+
url: target.url,
|
|
252
|
+
target: target.name,
|
|
253
|
+
protected: target.protected,
|
|
254
|
+
allowProtected: flags.allowProtected,
|
|
255
|
+
allowPooler: flags.allowPooler || config.allowPooler === true,
|
|
256
|
+
...flags.lockTimeoutMs !== undefined ? { lockTimeoutMs: flags.lockTimeoutMs } : config.timeouts?.lock !== undefined ? { lockTimeoutMs: config.timeouts.lock } : {},
|
|
257
|
+
...flags.statementTimeoutMs !== undefined ? { statementTimeoutMs: flags.statementTimeoutMs } : config.timeouts?.statement !== undefined ? { statementTimeoutMs: config.timeouts.statement } : {},
|
|
258
|
+
...backfillTiming2(config.backfill),
|
|
259
|
+
migrations,
|
|
260
|
+
snapshot: {
|
|
261
|
+
catalog: head.catalog,
|
|
262
|
+
migrationId: head.id,
|
|
263
|
+
catalogHash: head.catalogHash,
|
|
264
|
+
reference: built === undefined ? [] : readReference(built.tables, built.casing, head.catalog)
|
|
265
|
+
},
|
|
266
|
+
...config.roles !== undefined ? { migrationRole: config.roles.migration } : {},
|
|
267
|
+
lintDirectory: directory,
|
|
268
|
+
onProgress: (line) => {
|
|
269
|
+
if (write !== undefined)
|
|
270
|
+
write(`${line}
|
|
271
|
+
`);
|
|
272
|
+
else
|
|
273
|
+
progress.push(line);
|
|
274
|
+
}
|
|
275
|
+
});
|
|
276
|
+
const summary = formatReport(report);
|
|
277
|
+
if (write !== undefined || progress.length === 0)
|
|
278
|
+
return summary;
|
|
279
|
+
return `${progress.join(`
|
|
280
|
+
`)}
|
|
281
|
+
${summary}`;
|
|
282
|
+
}
|
|
283
|
+
async function pushProject2(cwd, flags) {
|
|
284
|
+
const config = await loadConfig(cwd);
|
|
285
|
+
const target = selectTarget(config, flags.target);
|
|
286
|
+
assertTargetPolicy(target, "push", flags.allowProtected);
|
|
287
|
+
const head = await projectHead(cwd);
|
|
288
|
+
const plan = planMigration({
|
|
289
|
+
before: head.previous,
|
|
290
|
+
after: head.catalog,
|
|
291
|
+
renames: head.renames,
|
|
292
|
+
name: "push",
|
|
293
|
+
...batchSizeField(config.backfill?.batchSize)
|
|
294
|
+
});
|
|
295
|
+
if (plan.steps.length === 0)
|
|
296
|
+
return `target ${target.name}
|
|
297
|
+
no changes
|
|
298
|
+
`;
|
|
299
|
+
const report = await applyTarget2({
|
|
300
|
+
url: target.url,
|
|
301
|
+
target: target.name,
|
|
302
|
+
protected: target.protected,
|
|
303
|
+
allowProtected: flags.allowProtected,
|
|
304
|
+
allowPooler: flags.allowPooler || config.allowPooler === true,
|
|
305
|
+
...backfillTiming2(config.backfill),
|
|
306
|
+
migrations: [
|
|
307
|
+
{
|
|
308
|
+
id: "push",
|
|
309
|
+
catalogHash: catalogHash2(head.catalog),
|
|
310
|
+
steps: plan.steps
|
|
311
|
+
}
|
|
312
|
+
],
|
|
313
|
+
...config.roles !== undefined ? { migrationRole: config.roles.migration } : {}
|
|
314
|
+
});
|
|
315
|
+
return formatReport(report);
|
|
316
|
+
}
|
|
317
|
+
function backfillTiming2(backfill) {
|
|
318
|
+
return {
|
|
319
|
+
...backfill?.pauseMs !== undefined ? { backfillPauseMs: backfill.pauseMs } : {},
|
|
320
|
+
...backfill?.statementTimeoutMs !== undefined ? { backfillStatementTimeoutMs: backfill.statementTimeoutMs } : {}
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
function formatReport(report) {
|
|
324
|
+
const role = report.setRole !== undefined ? `set role ${report.setRole}
|
|
325
|
+
` : "";
|
|
326
|
+
if (report.applied.length === 0)
|
|
327
|
+
return `target ${report.target}
|
|
328
|
+
${role}nothing to apply
|
|
329
|
+
`;
|
|
330
|
+
return `target ${report.target}
|
|
331
|
+
${role}${report.applied.map((id) => `applied ${id}`).join(`
|
|
332
|
+
`)}
|
|
333
|
+
`;
|
|
334
|
+
}
|
|
335
|
+
function joinMigrations(cwd, migrations) {
|
|
336
|
+
return join(cwd, migrations ?? "migrations");
|
|
337
|
+
}
|
|
338
|
+
async function setTimeouts(connection, request) {
|
|
339
|
+
const lock = request.lockTimeoutMs ?? DEFAULT_LOCK_MS;
|
|
340
|
+
const statement = request.statementTimeoutMs ?? DEFAULT_STATEMENT_MS;
|
|
341
|
+
await connection.execute("select set_config('lock_timeout', $1, false)", [String(lock)]);
|
|
342
|
+
await connection.execute("select set_config('statement_timeout', $1, false)", [
|
|
343
|
+
String(statement)
|
|
344
|
+
]);
|
|
345
|
+
}
|
|
346
|
+
async function lockTarget(connection, target) {
|
|
347
|
+
const result = await connection.execute("select pg_try_advisory_lock(hashtext($1))", [
|
|
348
|
+
lockKey(target)
|
|
349
|
+
]);
|
|
350
|
+
if (!wireTrue(result.rows[0]?.[0])) {
|
|
351
|
+
throw new OkmError2("OKM1522", `Another apply holds the lock for ${target}.`, {
|
|
352
|
+
kind: "conflict",
|
|
353
|
+
fix: {
|
|
354
|
+
summary: "Wait for that apply to finish. Do not start a second apply against the same target."
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function lockKey(target) {
|
|
360
|
+
return `okm:${target}`;
|
|
361
|
+
}
|
|
362
|
+
async function refusePendingLint(connection, directory, migrations) {
|
|
363
|
+
const pending = await pendingMigrationIds(connection, migrations);
|
|
364
|
+
const findings = lintMigrationDirectory(directory, pending);
|
|
365
|
+
if (hasError(findings))
|
|
366
|
+
throw lintRefusal(findings);
|
|
367
|
+
}
|
|
368
|
+
async function pendingMigrationIds(connection, migrations) {
|
|
369
|
+
const present = await connection.execute("select to_regclass('okm_history') is not null");
|
|
370
|
+
if (!wireTrue(present.rows[0]?.[0]))
|
|
371
|
+
return new Set(migrations.map((migration) => migration.id));
|
|
372
|
+
const history = await readHistory(connection);
|
|
373
|
+
const done = expandProvisioned(migrations, history.done, history.ids);
|
|
374
|
+
const pending = new Set;
|
|
375
|
+
for (const migration of migrations) {
|
|
376
|
+
for (let index = 0;index < migration.steps.length; index += 1) {
|
|
377
|
+
if (!done.has(`${migration.id}:${String(index)}`)) {
|
|
378
|
+
pending.add(migration.id);
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
return pending;
|
|
384
|
+
}
|
|
385
|
+
async function readHistory(connection) {
|
|
386
|
+
const result = await connection.execute("select migration_id, step_index::text from okm_history");
|
|
387
|
+
const done = new Set;
|
|
388
|
+
const ids = [];
|
|
389
|
+
for (const row of result.rows) {
|
|
390
|
+
const id = row[0];
|
|
391
|
+
const step = row[1];
|
|
392
|
+
if (id === null || id === undefined || step === null || step === undefined)
|
|
393
|
+
continue;
|
|
394
|
+
done.add(`${id}:${step}`);
|
|
395
|
+
ids.push(id);
|
|
396
|
+
}
|
|
397
|
+
return { done, ids };
|
|
398
|
+
}
|
|
399
|
+
async function provisionEmpty(connection, request, session) {
|
|
400
|
+
const snapshot = request.snapshot;
|
|
401
|
+
if (snapshot === undefined)
|
|
402
|
+
throw new Error("provision requires a snapshot");
|
|
403
|
+
assertTargetPolicy({ name: request.target, protected: request.protected }, "provision", request.allowProtected === true);
|
|
404
|
+
if (snapshot.reference.length > 0) {
|
|
405
|
+
assertTargetPolicy({ name: request.target, protected: request.protected }, "reference", request.allowProtected === true);
|
|
406
|
+
}
|
|
407
|
+
const schema = concreteSchema(request);
|
|
408
|
+
await connection.execute(META);
|
|
409
|
+
await connection.execute(HISTORY);
|
|
410
|
+
await connection.execute(BACKFILL);
|
|
411
|
+
await installSnapshot(connection, snapshot.catalog, schema, snapshot.reference, session, request.migrationRole);
|
|
412
|
+
const mark = provisionMark(snapshot.migrationId);
|
|
413
|
+
await connection.execute("begin");
|
|
414
|
+
try {
|
|
415
|
+
await connection.execute("insert into okm_history (migration_id, step_index, class, catalog_hash) values ($1, 0, 'expand', $2)", [mark, snapshot.catalogHash]);
|
|
416
|
+
await connection.execute(`insert into okm_meta (id, catalog_hash, migration_id) values ('head', $1, $2)
|
|
417
|
+
on conflict (id) do update set catalog_hash = excluded.catalog_hash, migration_id = excluded.migration_id`, [snapshot.catalogHash, mark]);
|
|
418
|
+
await connection.execute("commit");
|
|
419
|
+
} catch (error) {
|
|
420
|
+
await connection.execute("rollback").catch(() => {
|
|
421
|
+
return;
|
|
422
|
+
});
|
|
423
|
+
throw error;
|
|
424
|
+
}
|
|
425
|
+
return {
|
|
426
|
+
target: request.target,
|
|
427
|
+
applied: [mark],
|
|
428
|
+
...session.setRole !== undefined ? { setRole: session.setRole } : {}
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
async function insertReference(connection, request) {
|
|
432
|
+
const snapshot = request.snapshot;
|
|
433
|
+
if (snapshot === undefined || snapshot.reference.length === 0)
|
|
434
|
+
return;
|
|
435
|
+
assertTargetPolicy({ name: request.target, protected: request.protected }, "reference", request.allowProtected === true);
|
|
436
|
+
const statements = referenceInserts(snapshot.reference, concreteSchema(request));
|
|
437
|
+
if (statements.length === 0)
|
|
438
|
+
return;
|
|
439
|
+
await connection.execute("begin");
|
|
440
|
+
try {
|
|
441
|
+
for (const sql of statements)
|
|
442
|
+
await connection.execute(sql);
|
|
443
|
+
await connection.execute("commit");
|
|
444
|
+
} catch (error) {
|
|
445
|
+
await connection.execute("rollback").catch(() => {
|
|
446
|
+
return;
|
|
447
|
+
});
|
|
448
|
+
throw error;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
function concreteSchema(request) {
|
|
452
|
+
return request.searchPath ?? request.schema ?? "public";
|
|
453
|
+
}
|
|
454
|
+
async function targetState(connection) {
|
|
455
|
+
const meta = await connection.execute(`select to_regclass('okm_meta') is not null
|
|
456
|
+
or to_regclass('okm_history') is not null
|
|
457
|
+
or to_regclass('okm_backfill') is not null`);
|
|
458
|
+
if (wireTrue(meta.rows[0]?.[0])) {
|
|
459
|
+
const historyRows = await tableRows(connection, "okm_history");
|
|
460
|
+
const metaRows = await tableRows(connection, "okm_meta");
|
|
461
|
+
if (historyRows === 0 && metaRows === 0) {
|
|
462
|
+
const object = await namespaceObject(connection);
|
|
463
|
+
if (object === undefined)
|
|
464
|
+
return { kind: "empty" };
|
|
465
|
+
return { kind: "interrupted", object };
|
|
466
|
+
}
|
|
467
|
+
return { kind: "tracked" };
|
|
468
|
+
}
|
|
469
|
+
const object = await namespaceObject(connection);
|
|
470
|
+
if (object === undefined)
|
|
471
|
+
return { kind: "empty" };
|
|
472
|
+
return { kind: "occupied", object };
|
|
473
|
+
}
|
|
474
|
+
async function tableRows(connection, name) {
|
|
475
|
+
const present = await connection.execute(`select to_regclass('${name}') is not null`);
|
|
476
|
+
if (!wireTrue(present.rows[0]?.[0]))
|
|
477
|
+
return 0;
|
|
478
|
+
const counted = await connection.execute(`select count(*)::text from ${name}`);
|
|
479
|
+
const text = counted.rows[0]?.[0];
|
|
480
|
+
if (text === null || text === undefined)
|
|
481
|
+
return 0;
|
|
482
|
+
return Number(text);
|
|
483
|
+
}
|
|
484
|
+
async function namespaceObject(connection) {
|
|
485
|
+
const current = await connection.execute("select current_schema()");
|
|
486
|
+
const schema = current.rows[0]?.[0];
|
|
487
|
+
if (schema === null || schema === undefined)
|
|
488
|
+
return;
|
|
489
|
+
const found = await connection.execute(`select name from (
|
|
490
|
+
select c.relname as name from pg_class c
|
|
491
|
+
join pg_namespace n on n.oid = c.relnamespace
|
|
492
|
+
where n.nspname = $1
|
|
493
|
+
and c.relkind in ('r', 'p', 'v', 'm', 'S', 'f')
|
|
494
|
+
and c.relname not in ('okm_meta', 'okm_history', 'okm_backfill')
|
|
495
|
+
union all
|
|
496
|
+
select t.typname from pg_type t
|
|
497
|
+
join pg_namespace n on n.oid = t.typnamespace
|
|
498
|
+
where n.nspname = $1 and t.typtype in ('e', 'd')
|
|
499
|
+
union all
|
|
500
|
+
select e.extname from pg_extension e
|
|
501
|
+
join pg_namespace n on n.oid = e.extnamespace
|
|
502
|
+
where n.nspname = $1
|
|
503
|
+
) objects limit 1`, [schema]);
|
|
504
|
+
const object = found.rows[0]?.[0];
|
|
505
|
+
if (object === null || object === undefined)
|
|
506
|
+
return;
|
|
507
|
+
return object;
|
|
508
|
+
}
|
|
509
|
+
function interruptedProvision(target, object) {
|
|
510
|
+
return new OkmError2("OKM1851", `Target ${target} is not empty (${object}): a previous provision stopped part-way; drop the schema or database and run again`, {
|
|
511
|
+
kind: "forbidden",
|
|
512
|
+
fix: {
|
|
513
|
+
summary: "Drop the schema or database and run again. A partial provision is not repaired."
|
|
514
|
+
}
|
|
515
|
+
});
|
|
516
|
+
}
|
|
517
|
+
function notEmpty(target, object) {
|
|
518
|
+
const message = object === "okm_meta" ? `Target ${target} already has migration history and cannot be provisioned.` : `Target ${target} is not empty (${object}) and has no migration history.`;
|
|
519
|
+
return new OkmError2("OKM1851", message, {
|
|
520
|
+
kind: "forbidden",
|
|
521
|
+
fix: {
|
|
522
|
+
summary: "Provision an empty schema or an empty database. A non-empty target is refused."
|
|
523
|
+
}
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
async function prepareMigrationRole(connection, request, session) {
|
|
527
|
+
const role = request.migrationRole;
|
|
528
|
+
const changes = request.migrations.some((migration) => migration.steps.some((step) => changesRole(step.sql)));
|
|
529
|
+
if (role === undefined) {
|
|
530
|
+
if (changes)
|
|
531
|
+
await assertCreateRole(connection, await currentUser(connection));
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const current = await currentUser(connection);
|
|
535
|
+
const exists = await roleExists(connection, role);
|
|
536
|
+
if (changes)
|
|
537
|
+
await assertCreateRole(connection, exists && current !== role ? role : current);
|
|
538
|
+
if (exists && current !== role)
|
|
539
|
+
await assumeRole(connection, role, session);
|
|
540
|
+
}
|
|
541
|
+
function wireTrue(value) {
|
|
542
|
+
return value === "t" || value === "true";
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
export { applyTarget2, applyProject2, pushProject2, backfillTiming2 };
|
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
noticeBuffer,
|
|
3
3
|
WireSession,
|
|
4
4
|
cursorStream
|
|
5
|
-
} from "./
|
|
5
|
+
} from "./zvbjc949.js";
|
|
6
6
|
import {
|
|
7
7
|
BUNSQL_CAPABILITIES,
|
|
8
8
|
driverErrors,
|
|
@@ -41,7 +41,7 @@ function open5(config) {
|
|
|
41
41
|
counters.busy = Math.max(0, counters.busy - 1);
|
|
42
42
|
});
|
|
43
43
|
const held = { sql, parked, acquireMs, counters, notices, isClosed };
|
|
44
|
-
const checkout = () => closed ? rejectClosed() : import("./
|
|
44
|
+
const checkout = () => closed ? rejectClosed() : import("./4ef7mmqf.js").then((mod) => mod.checkout(held));
|
|
45
45
|
return {
|
|
46
46
|
capabilities: BUNSQL_CAPABILITIES,
|
|
47
47
|
execute(text, params, options) {
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import {
|
|
2
2
|
open2
|
|
3
|
-
} from "./
|
|
4
|
-
import"./
|
|
3
|
+
} from "./wqem4jed.js";
|
|
4
|
+
import"./8jb4ymh2.js";
|
|
5
5
|
import"./n442h54c.js";
|
|
6
6
|
import {
|
|
7
7
|
assertTargetPolicy,
|
|
8
8
|
selectTarget,
|
|
9
9
|
openProject
|
|
10
|
-
} from "./
|
|
11
|
-
import"./
|
|
10
|
+
} from "./q6zsyh31.js";
|
|
11
|
+
import"./n5y9zyj1.js";
|
|
12
|
+
import"./41kv2xdm.js";
|
|
12
13
|
import {
|
|
13
14
|
isExactVersion,
|
|
14
15
|
compareExtensionVersions
|
|
15
|
-
} from "./
|
|
16
|
+
} from "./8ktwd3rh.js";
|
|
16
17
|
import {
|
|
17
18
|
OkmError2
|
|
18
19
|
} from "./52szzakm.js";
|