Skip to content

Commit 00ca77b

Browse files
committed
test(objectql): pin the count and hooks of a matched row another matched row cascades into
A predicate delete walks its matched rows as one set, so a matched row that another matched row's cascade reaches is removed by the predicate's own id-set delete: the count includes it and its delete hooks fire once. The pending release note states this; the test holds it. Claude-Session: https://claude.ai/code/session_01Utp6CXc5hequf76uJfzrdK Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8dc35ab commit 00ca77b

1 file changed

Lines changed: 32 additions & 0 deletions

File tree

‎packages/objectql/src/engine-cascade-delete-atomic.test.ts‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -572,6 +572,38 @@ describe('[#26871] a predicate delete runs the referential walk for every matche
572572
expect(driver.writes).toEqual([]);
573573
expect(driver.rowsOf('detail')).toHaveLength(1);
574574
});
575+
576+
it('counts a matched row another matched row cascades into, and fires its delete hooks once (#27004)', async () => {
577+
// One walk over the matched set: the child is a member like its parent,
578+
// so the cascade leaves it to the predicate's own id-set delete instead of
579+
// deleting it through a nested delete of its own.
580+
const crew = {
581+
name: 'crew',
582+
label: 'Crew',
583+
fields: {
584+
id: { name: 'id', type: 'text' as const, primaryKey: true },
585+
name: { name: 'name', type: 'text' as const },
586+
lead: { name: 'lead', type: 'lookup' as const, reference: 'crew', deleteBehavior: 'cascade' },
587+
},
588+
};
589+
const { engine, driver } = await makeEngine([crew]);
590+
const lead = await engine.insert('crew', { name: 'doomed', lead: null });
591+
const member = await engine.insert('crew', { name: 'doomed', lead: lead.id });
592+
const kept = await engine.insert('crew', { name: 'kept', lead: null });
593+
const events: string[] = [];
594+
for (const event of ['beforeDelete', 'afterDelete'] as const) {
595+
engine.registerHook(event, async (ctx: any) => void events.push(`${event}:${ctx.input?.id}`), { object: 'crew' });
596+
}
597+
598+
const removed = await engine.delete('crew', { where: { name: 'doomed' }, multi: true });
599+
600+
expect(removed).toBe(2);
601+
expect(driver.rowsOf('crew').map((r: any) => r.id)).toEqual([kept.id]);
602+
expect([...events].sort()).toEqual([
603+
`afterDelete:${lead.id}`, `afterDelete:${member.id}`,
604+
`beforeDelete:${lead.id}`, `beforeDelete:${member.id}`,
605+
].sort());
606+
});
575607
});
576608

577609
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)