mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-08 01:57:31 +01:00
fix(db): tolerate missing constraints during clone upgrades
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
d001430ce4
commit
5ae7368656
2 files changed
+39
-1
No files matched your search
@@ -168,6 +168,37 @@ describeEmbeddedPostgres("resetPostgresDatabase", () => {
|
||||
});
|
||||
|
||||
describeEmbeddedPostgres("applyPendingMigrations", () => {
|
||||
it("upgrades an older clone whose replaced connection constraint is already absent", async () => {
|
||||
const clusterUrl = new URL(await createTempDatabase());
|
||||
const databaseName = "missing_connection_constraint";
|
||||
clusterUrl.pathname = "/postgres";
|
||||
await ensurePostgresDatabase(clusterUrl.toString(), databaseName);
|
||||
clusterUrl.pathname = "/" + databaseName;
|
||||
const connectionString = clusterUrl.toString();
|
||||
const prefixFolder = await fs.promises.mkdtemp(join(tmpdir(), "paperclip-migration-prefix-"));
|
||||
cleanups.push(() => fs.promises.rm(prefixFolder, { recursive: true, force: true }));
|
||||
await fs.promises.mkdir(join(prefixFolder, "meta"));
|
||||
const journal = JSON.parse(await fs.promises.readFile(new URL("./migrations/meta/_journal.json", import.meta.url), "utf8"));
|
||||
journal.entries = journal.entries.filter((entry: { idx: number }) => entry.idx < 255);
|
||||
await fs.promises.writeFile(join(prefixFolder, "meta/_journal.json"), JSON.stringify(journal));
|
||||
for (const entry of journal.entries) {
|
||||
await fs.promises.copyFile(new URL(`./migrations/${entry.tag}.sql`, import.meta.url), join(prefixFolder, `${entry.tag}.sql`));
|
||||
}
|
||||
const sql = postgres(connectionString, { max: 1, onnotice: () => {} });
|
||||
try {
|
||||
await migrate(drizzle(sql), { migrationsFolder: prefixFolder });
|
||||
await sql`ALTER TABLE "tool_connections" DROP CONSTRAINT "tool_connections_transport_check"`;
|
||||
await applyPendingMigrations(connectionString);
|
||||
expect((await inspectMigrations(connectionString)).status).toBe("upToDate");
|
||||
const [constraint] = await sql<{ definition: string }[]>`
|
||||
SELECT pg_get_constraintdef(oid) AS definition FROM pg_constraint
|
||||
WHERE conrelid = 'tool_connections'::regclass AND conname = 'tool_connections_transport_check'
|
||||
`;
|
||||
expect(constraint?.definition).toContain("chat_sdk");
|
||||
expect(constraint?.definition).toContain("runtime_auth");
|
||||
} finally { await sql.end(); }
|
||||
}, 30_000);
|
||||
|
||||
it("upgrades renumbered recovery migrations and replays their schema idempotently", async () => {
|
||||
const connectionString = await createTempDatabase();
|
||||
await applyPendingMigrations(connectionString);
|
||||
|
||||
@@ -494,7 +494,14 @@ async function applyPendingMigrationsManually(
|
||||
|
||||
await runInTransaction(sql, async () => {
|
||||
for (const statement of splitMigrationStatements(migrationContent)) {
|
||||
await sql.unsafe(statement);
|
||||
// Older dev schemas can already lack a constraint that the next
|
||||
// migration replaces. Keep the published SQL/hash intact and let
|
||||
// that harmless drop proceed; the replacement still runs normally.
|
||||
const executable = statement.replace(
|
||||
/^(\s*ALTER TABLE "[^"]+" DROP CONSTRAINT) (?!IF EXISTS\b)/i,
|
||||
"$1 IF EXISTS ",
|
||||
);
|
||||
await sql.unsafe(executable);
|
||||
}
|
||||
|
||||
await recordMigrationHistoryEntry(
|
||||
|
||||
Reference in new issue
Block a user