database-error
v2.0.2
Published
Turns errors from database libraries into more useful error objects
Downloads
20
Maintainers
Readme
database-error
A small library to make error objects from databases more programmatically useful. Currently supports:
- PostgreSQL: Through
pg
(notpg.native
!).
Database libraries tend to throw rather hard-to-process errors - often, they will contain a mishmash of information in a single error message string, with no clear way to filter or handle them programmatically. This library solves that by parsing the errors for you, and turning them into more consistent, useful, identifiable, and structured error objects.
Note that while Knex is supported (since it uses pg
internally, for talking to PostgreSQL), it is not required - this library should work fine with any other pg
-based implementation as well. Support for other (relational) database libraries is planned in the future, with the intention to provide a standardized format for useful database errors.
License
WTFPL or CC0, whichever you prefer. A donation and/or attribution are appreciated, but not required.
Donate
Maintaining open-source projects takes a lot of time, and the more donations I receive, the more time I can dedicate to open-source. If this module is useful to you, consider making a donation!
You can donate using Bitcoin, PayPal, Flattr, cash-in-mail, SEPA transfers, and pretty much anything else. Thank you!
Contributing
Pull requests welcome. Please make sure your modifications are in line with the overall code style, and ensure that you're editing the files in src/
, not those in lib/
.
Build tool of choice is gulp
; simply run gulp
while developing, and it will watch for changes.
Be aware that by making a pull request, you agree to release your modifications under the licenses stated above.
Usage
A simple example, detecting a duplicate entry in the database:
const Promise = require("bluebird");
const databaseError = require("database-error");
/* The below objects are used as 'object predicates' in Bluebird's .catch, to filter out the specific kind of database errors we're interested in. Errors are matched by the properties of the predicate object(s). */
let duplicateEmailAddress = {
name: "UniqueConstraintViolationError",
table: "users",
column: "email"
}
let duplicateUsername = {
name: "UniqueConstraintViolationError",
table: "users",
column: "username"
}
// ... assuming we have a Knex instance, and an Express application with an `express-promise-router` ...
router.post("/register", (req, res) => {
Promise.try(() => {
return knex("users").insert({
username: req.body.username,
email: req.body.emailAddress
}).returning("id");
}).then((id) => {
res.send(`Hello, user ${id[0]}!`);
}).catch(databaseError.rethrow).catch(duplicateEmailAddress, (err) => {
res.status(422).send("That e-mail address already exists! Please pick a different one.");
}).catch(duplicateUsername, (err) => {
res.status(422).send("That username already exists! Please pick a different one.");
})
});
This assumes the following:
- A
users
table exists, with anid
,username
andemail
column. - The
email
andusername
columns both have a UNIQUE constraint set on them, disallowing duplicates.
When a user attempts to register a new account, and either their provided username or e-mail address already exist in the database the following happens:
- A generic error will be thrown by the database library (eg.
pg
). - This error is picked up by
databaseError.rethrow
in the first.catch
statement, and turned into a more useful error (with the clearly distinguishableUniqueConstraintViolationError
name, and structuredtable
/column
properties). - This new, more useful error is rethrown by
databaseError.rethrow
. - The second and third
.catch
statement each receive the new error, check whether it has the properties they're looking for, and if so, trigger the corresponding error handler.
API
A brief listing of the currently defined error types, for easier searching:
- UniqueConstraintViolationError
- ForeignKeyConstraintViolationError
- NotNullConstraintViolationError
- CheckConstraintViolationError
- InvalidTypeError
- EnumError
- UndefinedColumnError
The full documentation for each type is below.
databaseError.rethrow(error)
Rethrows a more useful error, where possible.
- error: The original database error object to convert.
Throws:
- If the original error could be parsed: Throws the new, converted
database-error
error object. This will be one of the types specified below. - If the original error was not of a recognized type: Rethrows the original error, unmodified.
In practice, this means that unrecognized errors are "passed through", while recognized errors are converted into more useful errors.
databaseError.convert(error)
You would not normally use this method directly, and you should probably use databaseError.rethrow
instead.
Converts the specified error
into a more useful Error object.
- error: The original database error object to convert.
Returns/throws:
- If the original error could be parsed: Returns the new, converted
database-error
error object. This will be one of the types specified below. - If the original error was not of a recognized type: Throws a
databaseError.UnknownError
.
databaseError.UnknownError
Special error type that's thrown for databaseError.convert
when an unrecognized error is provided. You will never encounter this error when using databaseError.rethrow
, and it does not include any further information.
Database error types
databaseError.DatabaseError
"Base type" that all other error types (except for UnknownError
) are inheriting from. This can be useful for logging all recognized database errors.
However, note that you will never encounter a DatabaseError
directly - it purely functions as a base type, meaning that you need to use instanceof
to recognize an error as a DatabaseError
, and matching by name is not sufficient.
Every error inheriting from DatabaseError
will always contain the following properties:
- originalError: The original error being converted.
- pgCode: Only for PostgreSQL. The SQLSTATE error code from PostgreSQL.
- code: A unique, string-typed identifier for the error type.
In addition, different errors include different other properties. These are listed for each error type below.
databaseError.UniqueConstraintViolationError
This error is thrown when a query violates a UNIQUE
constraint. In practice, this means that there's an attempt to insert a duplicate entry into your database (see also the Usage section above).
A UniqueConstraintViolationError
will contain the following additional properties:
- schema: The schema in which the violation occurred.
- table: The table in which the violation occurred.
- For single-column
UNIQUE
constraints:- column: The column in which the violation occurred.
- value: The offending duplicate value.
- For multiple-column (composite)
UNIQUE
constraints:- columns: The columns in which the violation occurred, as an array.
- values: The offending duplicate values, as an array.
- isComposite: Whether the
UNIQUE
constraint involves multiple columns. Boolean. - constraint: The name of the
UNIQUE
constraint that is being violated.
Note that the keys for composite UNIQUE
constraints are plural, not singular. This is intentional, to provide a more predictable API.
databaseError.ForeignKeyConstraintViolationError
This error is thrown when a query violates a foreign key constraint. In practice, this means that there's an attempt to point a foreign key at a non-existent entry (usually in another table).
A ForeignKeyConstraintViolationError
will contain the following additional properties:
- schema: The schema in which the violation occurred.
- table: The table in which the violation occurred.
- column: The column in which the violation occurred.
- value: The offending foreign key (value).
- constraint: The name of the foreign key constraint that is being violated.
- foreignTable: The table to which the violating foreign key refers.
databaseError.NotNullConstraintViolationError
This error is thrown when a query violates a NOT NULL
constraint. In practice, this usually means that a required value is missing.
A NotNullConstraintViolationError
will contain the following additional properties:
- schema: The schema in which the violation occurred.
- table: The table in which the violation occurred.
- column: The column in which the violation occurred - ie. the column for which a required value is missing.
- values: An array of values, for the item being inserted/updated. Note that all these values are string representations, and they are intended purely for reference purposes.
- query: The query that caused the violation. This query may contain placeholders or be incomplete, and so is intended purely for reference purposes.
databaseError.CheckConstraintViolationError
This error is thrown when a query violates a CHECK
constraint. This can mean a lot of things (as CHECK
constraints can be fairly arbitrary), but one of the most common cases is probably an invalid value for a Knex-defined "enum", since Knex uses CHECK
constraints rather than native ENUMs.
An CheckConstraintViolationError
will contain the following additional properties:
- schema: The schema in which the violation occurred.
- table: The table in which the violation occurred.
- column: The column in which the violation occurred - ie. the column for which an invalid value was specified. This value is not always present. It is only present if the constraint name matches the
<table>_<column>_check
format (like is the case for Knex-defined "enum" columns). - values: An array of values, for the item being inserted/updated. Note that all these values are string representations, and they are intended purely for reference purposes.
- query: The query that caused the violation. This query may contain placeholders or be incomplete, and so is intended purely for reference purposes.
- constraint: The name of the
CHECK
constraint that is being violated.
databaseError.InvalidTypeError
This error is thrown when a query attempts to store a value of the wrong type in a column. Note that it does not always occur when storing a value of the wrong type, as some databases and libraries attempt to cast values automatically, and will only throw this error when they are unable to do so.
Unfortunately, when using PostgreSQL, InvalidTypeError
errors include very little information to pinpoint the problem, and there is no way for this library to obtain more information. You should make sure to validate value types carefully before attempting to insert them into the database, to prevent this type of error from occurring.
An InvalidTypeError
will contain the following additional properties:
- table: The table in which the invalid value was inserted. This value is not always present, and is purely for reference purposes. It is only available if the table name could be extracted from the query, and it may include a schema name as well.
- expectedType: The type of value that was expected for the column in question.
- value: The value that caused the type error.
- query: The query that caused the type error. This query may contain placeholders or be incomplete, and so is intended purely for reference purposes.
databaseError.EnumError
This error is thrown when a query attempts to store a value in an ENUM-type column, and that value isn't one of the allowed values.
Unfortunately, when using PostgreSQL, EnumError
errors include very little information to pinpoint the problem, and there is no way for this library to obtain more information. You should make sure to validate ENUM values in your application carefully before attempting to insert them into the database, to prevent this type of error from occurring.
An EnumError
will contain the following additional properties:
- table: The table in which the invalid value was inserted. This value is not always present, and is purely for reference purposes. It is only available if the table name could be extracted from the query, and it may include a schema name as well.
- enumType: The name of the ENUM type for which an invalid value was inserted.
- value: The value that caused the error.
- query: The query that caused the error. This query may contain placeholders or be incomplete, and so is intended purely for reference purposes.
databaseError.UndefinedColumnError
This error is thrown when a query attempts to store a value into a column that doesn't exist. A typical cause of this is forgetting to update the database schema after updating an application.
An UndefinedColumnError
will contain the following additional properties:
- table: The table in which the value was attempted to be inserted.
- table: The non-existent column in which the value was attempted to be inserted.
- query: The query that caused the type error. This query may contain placeholders or be incomplete, and so is intended purely for reference purposes.