Skip to content

Commit eb84ae2

Browse files
committed
test(objectql): bulk-write-per-row-hooks runs under the 10 s budget (#23182 row 5)
The five ceiling cases (section 4 and D6) pushed 10 001 rows through engine.insert each, about 40 us a row and 2.75 s of the file's 3.6 s of test wall. The ceiling is judged on what the driver's find returns, so how the rows got into the store is not what those cases pin: the stub driver gains a synchronous seed() that lays down the very rows create() stores (the engine adds nothing to a row on insert), and the five cases seed through it. Every assertion, hook, row count and the 60 s case timeout are unchanged; small seeds still go through engine.insert. Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX Co-authored-by: Claude <noreply@anthropic.com>
1 parent 140238a commit eb84ae2

1 file changed

Lines changed: 37 additions & 15 deletions

File tree

‎packages/objectql/src/bulk-write-per-row-hooks.test.ts‎

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -296,10 +296,10 @@ describe('[#5038] the matched row set is read ONCE', () => {
296296
describe('[#5038] an oversized matched set is refused, never silently downgraded', () => {
297297
it('rejects past the ceiling, naming the count, the limit and the routes out', async () => {
298298
const fired: string[] = [];
299-
const { engine } = await boot([hook('per_row', 'afterUpdate', () => { fired.push('x'); })]);
299+
const { engine, driver } = await boot([hook('per_row', 'afterUpdate', () => { fired.push('x'); })]);
300300

301301
const over = ObjectQL.MAX_BULK_PER_ROW_HOOK_ROWS + 1;
302-
await seedTasks(engine, Array.from({ length: over }, (_, i) => ({ title: `t${i}`, status: 'todo' })));
302+
seedCeilingRows(driver, over, 'todo');
303303

304304
const err = await engine
305305
.update('task', { status: 'done' }, { multi: true, where: { status: 'todo' } } as any)
@@ -322,9 +322,9 @@ describe('[#5038] an oversized matched set is refused, never silently downgraded
322322
});
323323

324324
it('does not apply to an object with no after-hooks — a big batch still writes', async () => {
325-
const { engine } = await boot([]);
325+
const { engine, driver } = await boot([]);
326326
const over = ObjectQL.MAX_BULK_PER_ROW_HOOK_ROWS + 1;
327-
await seedTasks(engine, Array.from({ length: over }, (_, i) => ({ title: `t${i}`, status: 'todo' })));
327+
seedCeilingRows(driver, over, 'todo');
328328

329329
const affected = await engine.update(
330330
'task', { status: 'done' }, { multi: true, where: { status: 'todo' } } as any,
@@ -870,8 +870,8 @@ describe('[#5574 / D6] one ceiling, BOTH phases, checked before the first dispat
870870

871871
it('refuses an over-ceiling batch on the BEFORE phase — zero handlers, zero rows written', async () => {
872872
const fired: string[] = [];
873-
const { engine } = await boot([hook('per_row_before', 'beforeUpdate', () => { fired.push('x'); })]);
874-
await seedTasks(engine, Array.from({ length: over }, (_, i) => ({ title: `t${i}`, status: 'todo' })));
873+
const { engine, driver } = await boot([hook('per_row_before', 'beforeUpdate', () => { fired.push('x'); })]);
874+
seedCeilingRows(driver, over, 'todo');
875875

876876
const err = await engine
877877
.update('task', { status: 'done' }, { multi: true, where: { status: 'todo' } })
@@ -890,8 +890,8 @@ describe('[#5574 / D6] one ceiling, BOTH phases, checked before the first dispat
890890

891891
it('refuses an over-ceiling bulk DELETE on the before phase too', async () => {
892892
const fired: string[] = [];
893-
const { engine } = await boot([hook('per_row_before', 'beforeDelete', () => { fired.push('x'); })]);
894-
await seedTasks(engine, Array.from({ length: over }, (_, i) => ({ title: `t${i}`, status: 'stale' })));
893+
const { engine, driver } = await boot([hook('per_row_before', 'beforeDelete', () => { fired.push('x'); })]);
894+
seedCeilingRows(driver, over, 'stale');
895895

896896
const err = await engine
897897
.delete('task', { multi: true, where: { status: 'stale' } })
@@ -908,12 +908,12 @@ describe('[#5574 / D6] one ceiling, BOTH phases, checked before the first dispat
908908
// refuse honest batches, which is the failure nobody reports as a bug.
909909
const beforeFired: string[] = [];
910910
const afterFired: string[] = [];
911-
const { engine } = await boot([
911+
const { engine, driver } = await boot([
912912
hook('pre', 'beforeUpdate', () => { beforeFired.push('x'); }),
913913
hook('post', 'afterUpdate', () => { afterFired.push('x'); }),
914914
]);
915915
const at = MAX_BULK_PER_ROW_HOOK_ROWS;
916-
await seedTasks(engine, Array.from({ length: at }, (_, i) => ({ title: `t${i}`, status: 'todo' })));
916+
seedCeilingRows(driver, at, 'todo');
917917

918918
const affected = await engine.update(
919919
'task', { status: 'done' }, { multi: true, where: { status: 'todo' } } as any,
@@ -1186,6 +1186,24 @@ async function seedTasks(engine: ObjectQL, rows: Record<string, unknown>[]): Pro
11861186
return Array.isArray(written) ? written : [written];
11871187
}
11881188

1189+
/**
1190+
* The matched set for the ceiling cases (section 4, D6): `count` task rows, all
1191+
* in `status`, written straight into the stub driver's store.
1192+
*
1193+
* Why not `seedTasks`: the ceiling is judged on what the driver's `find`
1194+
* returns, so how those rows got into the store is not what these cases pin —
1195+
* and pushing 10 001 rows through `engine.insert` cost about 40 µs a row, five
1196+
* times over (about 3 s of this file's 3.6 s of wall, on a quiet box). The
1197+
* stub's `create` is the only thing the engine's insert path ends in, and the
1198+
* engine adds nothing to a row on the way (a seeded row and an `engine.insert`
1199+
* one are the same `{ title, status, id }`), so `seed` lays down the very rows
1200+
* `seedTasks` would. Small seeds elsewhere in this file still go through
1201+
* `engine.insert`.
1202+
*/
1203+
function seedCeilingRows(driver: any, count: number, status: string): void {
1204+
driver.seed('task', Array.from({ length: count }, (_, i) => ({ title: `t${i}`, status })));
1205+
}
1206+
11891207
function makeStubDriver(): any {
11901208
const stores = new Map<string, Map<string, Record<string, unknown>>>();
11911209
const storeFor = (o: string) => {
@@ -1194,6 +1212,11 @@ function makeStubDriver(): any {
11941212
return s;
11951213
};
11961214
let nextId = 0;
1215+
const put = (o: string, data: Record<string, unknown>) => {
1216+
nextId += 1;
1217+
const id = (data.id as string) ?? `r_${nextId}`;
1218+
const row = { ...data, id }; storeFor(o).set(id, row); return row;
1219+
};
11971220
const matches = (row: Record<string, unknown>, where: any): boolean => {
11981221
if (!where || typeof where !== 'object') return true;
11991222
for (const [k, v] of Object.entries(where)) {
@@ -1217,11 +1240,10 @@ function makeStubDriver(): any {
12171240
return Array.from(storeFor(o).values()).filter((r) => matches(r, ast?.where));
12181241
},
12191242
async findOne(o: string, ast: any) { for (const r of storeFor(o).values()) if (matches(r, ast?.where)) return r; return null; },
1220-
async create(o: string, data: Record<string, unknown>) {
1221-
nextId += 1;
1222-
const id = (data.id as string) ?? `r_${nextId}`;
1223-
const row = { ...data, id }; storeFor(o).set(id, row); return row;
1224-
},
1243+
async create(o: string, data: Record<string, unknown>) { return put(o, data); },
1244+
/** Store `rows` exactly as `create` would, skipping the engine's insert path
1245+
* (see `seedCeilingRows`). Synchronous: nothing here awaits. */
1246+
seed(o: string, rows: Record<string, unknown>[]) { for (const r of rows) put(o, r); },
12251247
async update(o: string, id: string, data: Record<string, unknown>) {
12261248
d.updateCalls += 1;
12271249
const s = storeFor(o); const cur = s.get(id); if (!cur) return null;

0 commit comments

Comments
 (0)