diff --git a/doc/release-notes/changelog.md b/doc/release-notes/changelog.md index 00d1c2134..86b60af33 100644 --- a/doc/release-notes/changelog.md +++ b/doc/release-notes/changelog.md @@ -120,7 +120,7 @@ - `insertGraph()` and `upsertGraph()` stay under the number of values that a query can bind on SQL Server, PostgreSQL and SQLite when they insert, patch and remove many rows, and fetch many roots or related models - `upsertGraphAndFetch()` loads the relations of a `withGraphFetched()` on its query with their modifiers, named modifiers, aliases and options into the fetched models, like `insertGraphAndFetch()`, instead of ignoring the modifiers and aliases [#839](https://github.com/Vincit/objection.js/issues/839) - The error of `upsertGraph()` queries with other query builder calls names `$query()`, which adds such a call too [#1168](https://github.com/Vincit/objection.js/issues/1168) -- Errors of knex's `better-sqlite3` client are wrapped like those of `sqlite3`, e.g. a unique violation as a `UniqueViolationError` with its table and columns, where they reached the app as the driver's `SqliteError`, also in 3.x +- Errors of knex's `better-sqlite3` client are wrapped like those of `sqlite3`, e.g. a unique violation, also of a primary key, as a `UniqueViolationError` with its table and columns, where they reached the app as the driver's `SqliteError`, also in 3.x ### Types diff --git a/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/constraintMessage.js b/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/constraintMessage.js index e33106335..3fa539a3a 100644 --- a/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/constraintMessage.js +++ b/lib/dbErrors/parsers/sqlite/DBError/ConstraintViolationError/constraintMessage.js @@ -5,8 +5,10 @@ // puts the SQL with its values in front, so the last match is SQLite's own. export function getConstraintMessage(err, kind) { const text = `${kind} constraint failed`; + // A duplicate primary key is a unique violation, with its own code: + const codes = kind === 'UNIQUE' ? ['UNIQUE', 'PRIMARYKEY'] : [kind.replace(' ', '')]; const isKind = - err.code === `SQLITE_CONSTRAINT_${kind.replace(' ', '')}` || + codes.some((code) => err.code === `SQLITE_CONSTRAINT_${code}`) || (err.code === 'SQLITE_CONSTRAINT' && /.*SQLITE_CONSTRAINT: (.*)$/s.exec(err.message)?.[1].startsWith(text)); diff --git a/tests/unit/dbErrors/sqlite.js b/tests/unit/dbErrors/sqlite.js index b59273eee..a8aff269a 100644 --- a/tests/unit/dbErrors/sqlite.js +++ b/tests/unit/dbErrors/sqlite.js @@ -48,6 +48,18 @@ describe('SQLite database errors', () => { expect(err).toMatchObject({ table: 't', columns: ['a', 'b'], client: 'sqlite' }); }); + it('should wrap primary key violations as unique violations', () => { + const err = wrapError( + createError( + 'SQLITE_CONSTRAINT', + 'SQLITE_CONSTRAINT_PRIMARYKEY', + 'UNIQUE constraint failed: p.id', + ), + ); + expect(err).toBeInstanceOf(UniqueViolationError); + expect(err).toMatchObject({ table: 'p', columns: ['id'] }); + }); + it('should wrap not null violations', () => { const err = wrapError( createError(