Skip to content

Commit c79eb9c

Browse files
os-devclaude
andcommitted
test(driver-sql): pin the three-state collision routing on Postgres and MySQL shapes (#5495)
The SQLite tests cover the path end to end, but SQLite is the one dialect where the decision is easy. Postgres names a column in its DETAIL line (state 1/2) or a composite (state 3); MySQL names only an index and so can only ever reach state 3 — which means on MySQL the data probe is not a fallback, it is the whole mechanism. This package's unit suite boots SQLite only, so the shapes are injected rather than driven through live servers — same method and same reason as sql-driver-unique-violation-predicate.test.ts. The state-2 case asserts the part that matters most: a named column that is not ours ends the matter WITHOUT probing, even when the probe would have said yes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015XLbsWE5G58ybd1Leq6bNg
1 parent 41a5fd3 commit c79eb9c

1 file changed

Lines changed: 86 additions & 0 deletions

File tree

packages/drivers/driver-sql/src/sql-driver-autonumber-resync.test.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,92 @@ describe('[#5495] autonumber sequence re-seed on a provable collision', () => {
236236
).toBeUndefined();
237237
});
238238

239+
/**
240+
* The three-state routing, held against the shapes Postgres and MySQL
241+
* actually hand knex.
242+
*
243+
* This package's unit suite boots SQLite only, so the other two dialects'
244+
* errors are INJECTED rather than driven through a live server — the same
245+
* method, and the same reason, as `sql-driver-unique-violation-predicate`.
246+
* What it buys: the routing is what decides whether a caller's 409 survives,
247+
* and on MySQL — where `uniqueViolationColumn` can never answer, by contract
248+
* — state 3 is the only state that exists. Reasoning about that limb is not
249+
* the same as pinning it.
250+
*/
251+
describe('three-state routing on the dialects this package ships', () => {
252+
const reservation = {
253+
field: 'case_number',
254+
tableName: 'crm_case',
255+
prefix: 'CASE-',
256+
scope: '',
257+
suffix: '',
258+
tenantField: 'organization_id',
259+
tenantId: 'orgA',
260+
value: 'CASE-00001',
261+
};
262+
263+
/** Route one injected error, against a table where `CASE-00001` DOES exist. */
264+
const route = async (error: unknown) => {
265+
const colliding = await (driver as any).collidingAutoNumberReservations(error, [reservation]);
266+
return colliding.map((r: any) => r.field);
267+
};
268+
269+
beforeEach(async () => {
270+
await driver.initObjects([CRM_CASE]);
271+
await knex()('crm_case').insert({ id: 'taken', organization_id: 'orgA', case_number: 'CASE-00001', title: 'taken' });
272+
});
273+
274+
it('state 1 — Postgres names our column in its DETAIL line', async () => {
275+
const err: any = new Error('insert into "crm_case" … - duplicate key value violates unique constraint "uniq_crm_case_case_number"');
276+
err.code = '23505';
277+
err.detail = 'Key (case_number)=(CASE-00001) already exists.';
278+
expect(uniqueViolationColumn(err)).toBe('case_number');
279+
expect(await route(err)).toEqual(['case_number']);
280+
});
281+
282+
it('state 2 — Postgres names a column the CALLER supplied, so nothing is retried', async () => {
283+
const err: any = new Error('insert into "crm_case" … - duplicate key value violates unique constraint "uniq_crm_case_email"');
284+
err.code = '23505';
285+
err.detail = 'Key (email)=(dup@example.com) already exists.';
286+
expect(uniqueViolationColumn(err)).toBe('email');
287+
// Decided WITHOUT probing the data — and the data would have said yes,
288+
// since CASE-00001 is present. That is the whole point: a named column
289+
// that is not ours ends the matter.
290+
expect(await route(err)).toEqual([]);
291+
});
292+
293+
it('state 3 — Postgres composite DETAIL names no single column, so the DATA decides', async () => {
294+
const err: any = new Error('insert into "crm_case" … - duplicate key value violates unique constraint "uniq_crm_case_org_case"');
295+
err.code = '23505';
296+
err.detail = 'Key (organization_id, case_number)=(orgA, CASE-00001) already exists.';
297+
expect(uniqueViolationColumn(err)).toBeUndefined();
298+
expect(await route(err)).toEqual(['case_number']);
299+
});
300+
301+
it('state 3 — MySQL can only ever name an index, so the DATA decides there always', async () => {
302+
const err: any = new Error("insert into `crm_case` … - ER_DUP_ENTRY: Duplicate entry 'orgA-CASE-00001' for key 'uniq_crm_case_org_case'");
303+
err.errno = 1062;
304+
err.code = 'ER_DUP_ENTRY';
305+
expect(isUniqueViolationError(err)).toBe(true);
306+
expect(uniqueViolationColumn(err)).toBeUndefined();
307+
expect(await route(err)).toEqual(['case_number']);
308+
});
309+
310+
it('state 3 — but an undeterminable violation on someone ELSE’s value is still not ours', async () => {
311+
const err: any = new Error("insert into `crm_case` … - ER_DUP_ENTRY: Duplicate entry 'x' for key 'uniq_crm_case_email'");
312+
err.errno = 1062;
313+
const other = { ...reservation, value: 'CASE-99999' }; // never written
314+
const colliding = await (driver as any).collidingAutoNumberReservations(err, [other]);
315+
expect(colliding).toEqual([]);
316+
});
317+
318+
it('a failure that is not a unique violation at all is never routed here', async () => {
319+
const err: any = new Error('insert into `crm_case` … - NOT NULL constraint failed: crm_case.title');
320+
expect(isUniqueViolationError(err)).toBe(false);
321+
expect(await route(err)).toEqual([]);
322+
});
323+
});
324+
239325
it('gives the caller the original error when the collision is not the counter’s', async () => {
240326
// A value the caller supplies explicitly is not a reservation, so no probe
241327
// and no retry can apply to it even though it IS the autonumber column.

0 commit comments

Comments
 (0)