No Description

Sven Slootweg 899ea8dab9 2.0.1 3 years ago
src 1e15128a8c Make composite UNIQUE constraint violation error message slightly more readable 3 years ago
test cf7e5f6180 Fix some internal function names 3 years ago
.gitignore 262a79ae03 Initial release, v0.0.1 3 years ago
.npmignore 262a79ae03 Initial release, v0.0.1 3 years ago
CHANGELOG.md 89cb8c5852 Add changelog entry for 2.0.1 3 years ago
README.md 082b49b932 Add support for 'undefined column' errors and composite UNIQUE constraint violations, and clarify the documentation 3 years ago
gulpfile.js 262a79ae03 Initial release, v0.0.1 3 years ago
index.js 262a79ae03 Initial release, v0.0.1 3 years ago
package.json 899ea8dab9 2.0.1 3 years ago

README.md

database-error

A small library to make error objects from databases more programmatically useful. Currently supports:

  • PostgreSQL: Through pg (not pg.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 an id, username and email column.
  • The email and username 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:

  1. A generic error will be thrown by the database library (eg. pg).
  2. This error is picked up by databaseError.rethrow in the first .catch statement, and turned into a more useful error (with the clearly distinguishable UniqueConstraintViolationError name, and structured table/column properties).
  3. This new, more useful error is rethrown by databaseError.rethrow.
  4. 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.