Skip to content

Commit 357740a

Browse files
committed
fix(projects): pin operator schema and pace detach repairs
1 parent 14554d0 commit 357740a

2 files changed

Lines changed: 65 additions & 1 deletion

File tree

‎apps/sim/scripts/backfill-projects.ts‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -204,8 +204,10 @@ The registered migration first commits the durable column-authority switch after
204204
const maxBatches = boundedInteger(values['max-batches'], 100, 10000)
205205
const seconds = boundedInteger(values.seconds, 60, 3600)
206206
const pauseMs = boundedInteger(values['pause-ms'], 100, 60000)
207+
/** URL startup parameters override driver options; pin the operator on every connection. */
208+
url.searchParams.set('search_path', 'public, pg_temp')
207209
const sql = postgres(
208-
rawUrl,
210+
url.toString(),
209211
withUtcTimestamps({
210212
max: 1,
211213
idle_timeout: 0,
@@ -434,6 +436,7 @@ The registered migration first commits the durable column-authority switch after
434436
error: describeError(error),
435437
})
436438
}
439+
await sleep(pauseMs)
437440
}
438441
} else {
439442
for (let position = 0; position < pending.length; ) {

‎packages/db/scripts/project-contract.integration.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -629,6 +629,67 @@ describe('Project expand/backfill/contract against PostgreSQL', () => {
629629
})
630630
})
631631

632+
it('keeps operator discovery, assignment and verification on public under a shadow URL search path', async () => {
633+
await database(async (sql, url) => {
634+
const directory = await mkdtemp(join(tmpdir(), 'project-operator-shadow-'))
635+
const manifest = join(directory, 'manifest.json')
636+
const report = join(directory, 'report.json')
637+
const scopedUrl = new URL(url)
638+
scopedUrl.searchParams.set('search_path', 'shadow,public')
639+
const run = (command: string) =>
640+
promisify(execFile)(
641+
'bun',
642+
[
643+
'--no-env-file',
644+
'scripts/backfill-projects.ts',
645+
command,
646+
'--manifest',
647+
manifest,
648+
...(command === 'plan'
649+
? []
650+
: ['--report', report, '--ack-release-drained', '--pause-ms', '1']),
651+
],
652+
{
653+
cwd: new URL('../../../apps/sim/', import.meta.url),
654+
env: { ...process.env, MIGRATION_DATABASE_URL: scopedUrl.toString() },
655+
timeout: 30000,
656+
}
657+
)
658+
try {
659+
await sql`INSERT INTO workspace (id,name,owner_id) VALUES ('public-env','Public','owner')`
660+
await sql`CREATE SCHEMA shadow`
661+
for (const name of [
662+
'workspace',
663+
'project',
664+
'workflow',
665+
'user',
666+
'organization',
667+
'project_workspace',
668+
]) {
669+
await sql`CREATE TABLE ${sql(`shadow.${name}`)} (LIKE ${sql(`public.${name}`)} INCLUDING ALL)`
670+
}
671+
await sql`INSERT INTO shadow.workspace (id,name,owner_id) VALUES ('shadow-env','Untouched','owner')`
672+
await run('plan')
673+
const plan = JSON.parse(await readFile(manifest, 'utf8'))
674+
expect(plan.families.map((family: { rootId: string }) => family.rootId)).toEqual([
675+
'public-env',
676+
])
677+
await run('apply')
678+
expect(await sql`SELECT id,project_id FROM public.workspace`).toEqual([
679+
{ id: 'public-env', project_id: expect.any(String) },
680+
])
681+
expect(await sql`SELECT count(*)::int AS count FROM public.project`).toEqual([{ count: 1 }])
682+
await run('verify')
683+
expect(await sql`SELECT id,project_id FROM shadow.workspace`).toEqual([
684+
{ id: 'shadow-env', project_id: null },
685+
])
686+
expect(await sql`SELECT count(*)::int AS count FROM shadow.project`).toEqual([{ count: 0 }])
687+
} finally {
688+
await rm(directory, { recursive: true, force: true })
689+
}
690+
})
691+
})
692+
632693
it('runs the real operator CLI read-only, resumes bounded batches and survives a lost checkpoint', async () => {
633694
await database(async (sql, url) => {
634695
const directory = await mkdtemp(join(tmpdir(), 'project-operator-test-'))

0 commit comments

Comments
 (0)