├── .gitignore ├── .npmignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── errors.json ├── errors_to_json.sed ├── index.js ├── npm-shrinkwrap.json ├── package.json └── test ├── index_test.js └── mocha.opts /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules/ 2 | /*.tgz 3 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /*.tgz 2 | /errors_to_json.sed 3 | /npm-shrinkwrap.json 4 | /.travis.yml 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "4" 4 | - "6" 5 | 6 | addons: 7 | postgresql: "9.6" 8 | 9 | before_script: make db/create PGHOST=localhost PGUSER=postgres 10 | script: make spec PGHOST=localhost PGUSER=postgres 11 | 12 | notifications: 13 | email: ["andri@dot.ee"] 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 1.1.0 (Jul 3, 2020) 2 | - Update PostgreSQL error names. 3 | 4 | ## 1.0.0 (Apr 16, 2015) 5 | - This cluster's hitting 80mph. 6 | No changes from v0.1.338 though. Just stable enough for v1.0.0. 7 | 8 | ## 0.1.338 (Dec 17, 2014) 9 | - Adds a description and keywords to `package.json`. 10 | 11 | ## 0.1.337 (Dec 17, 2014) 12 | - First off by one release. 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | PgError.js 2 | Copyright (C) 2014– Andri Möll 3 | 4 | This program is free software: you can redistribute it and/or modify it under 5 | the terms of the GNU Affero General Public License as published by the Free 6 | Software Foundation, either version 3 of the License, or any later version. 7 | 8 | Additional permission under the GNU Affero GPL version 3 section 7: 9 | If you modify this Program, or any covered work, by linking or 10 | combining it with other code, such other code is not for that reason 11 | alone subject to any of the requirements of the GNU Affero GPL version 3. 12 | 13 | In summary: 14 | - You can use this program for no cost. 15 | - You can use this program for both personal and commercial reasons. 16 | - You do not have to share your own program's code which uses this program. 17 | - You have to share modifications (e.g bug-fixes) you've made to this program. 18 | 19 | For the full copy of the GNU Affero General Public License see: 20 | http://www.gnu.org/licenses. 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | NODE_OPTS = --harmony 2 | TEST_OPTS = 3 | ERRORS_URL = https://raw.githubusercontent.com/postgres/postgres/master/src/backend/utils/errcodes.txt 4 | 5 | love: 6 | @echo "Feel like makin' love." 7 | 8 | test: 9 | @node $(NODE_OPTS) ./node_modules/.bin/_mocha -R dot $(TEST_OPTS) 10 | 11 | spec: 12 | @node $(NODE_OPTS) ./node_modules/.bin/_mocha -R spec $(TEST_OPTS) 13 | 14 | autotest: 15 | @node $(NODE_OPTS) ./node_modules/.bin/_mocha -R dot --watch $(TEST_OPTS) 16 | 17 | autospec: 18 | @node $(NODE_OPTS) ./node_modules/.bin/_mocha -R spec --watch $(TEST_OPTS) 19 | 20 | pack: 21 | @file=$$(npm pack); echo "$$file"; tar tf "$$file" 22 | 23 | publish: 24 | npm publish 25 | 26 | tag: 27 | git tag "v$$(node -e 'console.log(require("./package").version)')" 28 | 29 | errors.json: 30 | wget $(ERRORS_URL) -O- | sed -Ef errors_to_json.sed > "$@" 31 | 32 | db/create: 33 | createdb -E utf8 -T template0 pg_error_test 34 | 35 | db/drop: 36 | dropdb pg_error_test 37 | 38 | shrinkwrap: 39 | npm shrinkwrap --dev 40 | 41 | clean: 42 | rm -f *.tgz 43 | npm prune --production 44 | 45 | .PHONY: love 46 | .PHONY: test spec autotest autospec 47 | .PHONY: pack publish tag 48 | .PHONY: errors.json 49 | .PHONY: db/create db/drop 50 | .PHONY: shrinkwrap 51 | .PHONY: clean 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | PgError.js 2 | ========== 3 | [![NPM version][npm-badge]](https://www.npmjs.com/package/pg-error) 4 | [![Build status][travis-badge]](https://travis-ci.org/moll/node-pg-error) 5 | 6 | **PgError.js** is an error class for Node.js that parses [PostgreSQL's 7 | ErrorResponse format][pg-formats] and names its fields with **human readable 8 | properties**. It's most useful when combined with [Brian Carlson][brianc]'s 9 | [Node.js PostgreSQL client library][node-pg] to get **structured** and 10 | **identifiable** PostgreSQL errors. Supports all fields returned by PostgreSQL 11 | up to v9.4. 12 | 13 | The PostgreSQL client library does return an `Error` object with some fields 14 | set, but it's not a dedicated object for easy `instanceof` identification nor 15 | does it yet support all field types. I've found it immensely useful to be 16 | strict and classify errors beforehand when building fault tolerant systems. That 17 | helps programmatically decide whether to wrap, escalate or handle a particular 18 | error. 19 | 20 | [npm-badge]: https://img.shields.io/npm/v/pg-error.svg 21 | [travis-badge]: https://travis-ci.org/moll/node-pg-error.svg?branch=master 22 | [pg-formats]: http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html 23 | [pg-fields]: http://www.postgresql.org/docs/9.3/static/protocol-error-fields.html 24 | [brianc]: https://github.com/brianc 25 | [node-pg]: https://github.com/brianc/node-postgres 26 | 27 | 28 | Installing 29 | ---------- 30 | ``` 31 | npm install pg-error 32 | ``` 33 | 34 | PgError.js follows [semantic versioning](http://semver.org/), so feel free to 35 | depend on its major version with something like `>= 1.0.0 < 2` (a.k.a `^1.0.0`). 36 | 37 | 38 | Using 39 | ----- 40 | ```javascript 41 | var PgError = require("pg-error") 42 | 43 | var error = new PgError({ 44 | M: "null value in column \"name\" violates not-null constraint", 45 | S: "ERROR", 46 | C: "23502" 47 | }) 48 | 49 | error instanceof PgError // => true 50 | error instanceof Error // => true 51 | 52 | error.message // => "null value in column \"name\" violates not-null constraint" 53 | error.severity // => "ERROR" 54 | error.code // => 23502 55 | ``` 56 | 57 | ### Parsing [PostgreSQL's ErrorResponse][pg-formats] 58 | ```javascript 59 | var msg = new Buffer("MWash your teeth!\0SWARNING\0\0") 60 | var error = PgError.parse(msg) 61 | error.message // => "Wash your teeth!" 62 | error.severity // => "WARNING" 63 | ``` 64 | 65 | ### Using with [Node.js PostgreSQL client library][node-pg] 66 | The client does its error and notice parsing on the `Pg.Connection` object. 67 | You can get an active instance of it after connecting from `Pg.Client`: 68 | 69 | ```javascript 70 | var Pg = require("pg") 71 | var pg = new Pg.Client({host: "/tmp", database: "pg_error_test"}) 72 | var connection = pg.connection 73 | ``` 74 | 75 | You'll have to swap out two functions, `Pg.Connection.prototype.parseE` and 76 | `Pg.Connection.prototype.parseN`, for parsing errors and notices respectively: 77 | 78 | ```javascript 79 | connection.parseE = PgError.parse 80 | connection.parseN = PgError.parse 81 | ``` 82 | 83 | If you want every connection instance to parse errors to `PgError`, set them on the `Pg.Connection` prototype: 84 | 85 | ```javascript 86 | Pg.Connection.prototype.parseE = PgError.parse 87 | Pg.Connection.prototype.parseN = PgError.parse 88 | ``` 89 | 90 | However, the way the client is built, it will start emitting those errors and 91 | notices under the `PgError` event name. Until that's improved in the 92 | `Pg.Connection` class, you'll need to re-emit those under the correct `error` 93 | and `notice` events: 94 | 95 | ```javascript 96 | function emitPgError(err) { 97 | switch (err.severity) { 98 | case "ERROR": 99 | case "FATAL": 100 | case "PANIC": return this.emit("error", err) 101 | default: return this.emit("notice", err) 102 | } 103 | } 104 | 105 | connection.on("PgError", emitPgError) 106 | ``` 107 | 108 | That's it. Your Pg query errors should now be instances of `PgError` and with 109 | all the human readable field names. 110 | 111 | ### Using with [Knex.js](http://knexjs.org/) 112 | Using PgError.js with Knex.js is similar to using it with the plain Node.js PostgreSQL client library described above. Because Knex.js has a connection pool, you'll have to hook PgError.js in on every newly created connection: 113 | 114 | ```javascript 115 | var Knex = require("knex") 116 | 117 | Knex({ 118 | pool: { 119 | min: 1, 120 | max: 10, 121 | afterCreate: function(connection, done) { 122 | connection.connection.parseE = PgError.parse 123 | connection.connection.parseN = PgError.parse 124 | connection.connection.on("PgError", emitPgError) 125 | done() 126 | } 127 | } 128 | }) 129 | ``` 130 | 131 | The `emitPgError` function is listed above. 132 | 133 | ### Properties on an instance of PgError 134 | For descriptions of the properties, please see [PostgreSQL's Error and Notice 135 | Message Fields][pg-fields]. 136 | 137 | Property | Field | Description 138 | -----------------|---|---------------- 139 | severity | S | 140 | code | C | 141 | condition | | Code name in lowercase according to [errcodes.txt][]. 142 | detail | D | 143 | hint | H | 144 | position | P | Position parsed to a `Number`. 145 | internalPosition | p | Internal position parsed to a `Number`. 146 | internalQuery | q | 147 | where | W | 148 | schema | s | 149 | table | t | 150 | column | c | 151 | dataType | d | 152 | constraint | n | 153 | file | F | 154 | line | L | Line parsed to a `Number`. 155 | routine | R | 156 | 157 | [errcodes.txt]: https://github.com/postgres/postgres/blob/master/src/backend/utils/errcodes.txt 158 | 159 | 160 | License 161 | ------- 162 | PgError.js is released under a *Lesser GNU Affero General Public License*, which 163 | in summary means: 164 | 165 | - You **can** use this program for **no cost**. 166 | - You **can** use this program for **both personal and commercial reasons**. 167 | - You **do not have to share your own program's code** which uses this program. 168 | - You **have to share modifications** (e.g. bug-fixes) you've made to this 169 | program. 170 | 171 | For more convoluted language, see the `LICENSE` file. 172 | 173 | 174 | About 175 | ----- 176 | **[Andri Möll][moll]** typed this and the code. 177 | [Monday Calendar][monday] supported the engineering work. 178 | 179 | If you find PgError.js needs improving, please don't hesitate to type to me now 180 | at [andri@dot.ee][email] or [create an issue online][issues]. 181 | 182 | [email]: mailto:andri@dot.ee 183 | [issues]: https://github.com/moll/node-pg-error/issues 184 | [moll]: https://m811.com 185 | [monday]: https://mondayapp.com 186 | -------------------------------------------------------------------------------- /errors.json: -------------------------------------------------------------------------------- 1 | { 2 | "00000": "successful_completion", 3 | "01000": "warning", 4 | "0100C": "dynamic_result_sets_returned", 5 | "01008": "implicit_zero_bit_padding", 6 | "01003": "null_value_eliminated_in_set_function", 7 | "01007": "privilege_not_granted", 8 | "01006": "privilege_not_revoked", 9 | "01004": "string_data_right_truncation", 10 | "01P01": "deprecated_feature", 11 | "02000": "no_data", 12 | "02001": "no_additional_dynamic_result_sets_returned", 13 | "03000": "sql_statement_not_yet_complete", 14 | "08000": "connection_exception", 15 | "08003": "connection_does_not_exist", 16 | "08006": "connection_failure", 17 | "08001": "sqlclient_unable_to_establish_sqlconnection", 18 | "08004": "sqlserver_rejected_establishment_of_sqlconnection", 19 | "08007": "transaction_resolution_unknown", 20 | "08P01": "protocol_violation", 21 | "09000": "triggered_action_exception", 22 | "0A000": "feature_not_supported", 23 | "0B000": "invalid_transaction_initiation", 24 | "0F000": "locator_exception", 25 | "0F001": "invalid_locator_specification", 26 | "0L000": "invalid_grantor", 27 | "0LP01": "invalid_grant_operation", 28 | "0P000": "invalid_role_specification", 29 | "0Z000": "diagnostics_exception", 30 | "0Z002": "stacked_diagnostics_accessed_without_active_handler", 31 | "20000": "case_not_found", 32 | "21000": "cardinality_violation", 33 | "22000": "data_exception", 34 | "2202E": "array_subscript_error", 35 | "22021": "character_not_in_repertoire", 36 | "22008": "datetime_field_overflow", 37 | "22012": "division_by_zero", 38 | "22005": "error_in_assignment", 39 | "2200B": "escape_character_conflict", 40 | "22022": "indicator_overflow", 41 | "22015": "interval_field_overflow", 42 | "2201E": "invalid_argument_for_logarithm", 43 | "22014": "invalid_argument_for_ntile_function", 44 | "22016": "invalid_argument_for_nth_value_function", 45 | "2201F": "invalid_argument_for_power_function", 46 | "2201G": "invalid_argument_for_width_bucket_function", 47 | "22018": "invalid_character_value_for_cast", 48 | "22007": "invalid_datetime_format", 49 | "22019": "invalid_escape_character", 50 | "2200D": "invalid_escape_octet", 51 | "22025": "invalid_escape_sequence", 52 | "22P06": "nonstandard_use_of_escape_character", 53 | "22010": "invalid_indicator_parameter_value", 54 | "22023": "invalid_parameter_value", 55 | "22013": "invalid_preceding_or_following_size", 56 | "2201B": "invalid_regular_expression", 57 | "2201W": "invalid_row_count_in_limit_clause", 58 | "2201X": "invalid_row_count_in_result_offset_clause", 59 | "2202H": "invalid_tablesample_argument", 60 | "2202G": "invalid_tablesample_repeat", 61 | "22009": "invalid_time_zone_displacement_value", 62 | "2200C": "invalid_use_of_escape_character", 63 | "2200G": "most_specific_type_mismatch", 64 | "22004": "null_value_not_allowed", 65 | "22002": "null_value_no_indicator_parameter", 66 | "22003": "numeric_value_out_of_range", 67 | "2200H": "sequence_generator_limit_exceeded", 68 | "22026": "string_data_length_mismatch", 69 | "22001": "string_data_right_truncation", 70 | "22011": "substring_error", 71 | "22027": "trim_error", 72 | "22024": "unterminated_c_string", 73 | "2200F": "zero_length_character_string", 74 | "22P01": "floating_point_exception", 75 | "22P02": "invalid_text_representation", 76 | "22P03": "invalid_binary_representation", 77 | "22P04": "bad_copy_file_format", 78 | "22P05": "untranslatable_character", 79 | "2200L": "not_an_xml_document", 80 | "2200M": "invalid_xml_document", 81 | "2200N": "invalid_xml_content", 82 | "2200S": "invalid_xml_comment", 83 | "2200T": "invalid_xml_processing_instruction", 84 | "22030": "duplicate_json_object_key_value", 85 | "22031": "invalid_argument_for_sql_json_datetime_function", 86 | "22032": "invalid_json_text", 87 | "22033": "invalid_sql_json_subscript", 88 | "22034": "more_than_one_sql_json_item", 89 | "22035": "no_sql_json_item", 90 | "22036": "non_numeric_sql_json_item", 91 | "22037": "non_unique_keys_in_a_json_object", 92 | "22038": "singleton_sql_json_item_required", 93 | "22039": "sql_json_array_not_found", 94 | "2203A": "sql_json_member_not_found", 95 | "2203B": "sql_json_number_not_found", 96 | "2203C": "sql_json_object_not_found", 97 | "2203D": "too_many_json_array_elements", 98 | "2203E": "too_many_json_object_members", 99 | "2203F": "sql_json_scalar_required", 100 | "23000": "integrity_constraint_violation", 101 | "23001": "restrict_violation", 102 | "23502": "not_null_violation", 103 | "23503": "foreign_key_violation", 104 | "23505": "unique_violation", 105 | "23514": "check_violation", 106 | "23P01": "exclusion_violation", 107 | "24000": "invalid_cursor_state", 108 | "25000": "invalid_transaction_state", 109 | "25001": "active_sql_transaction", 110 | "25002": "branch_transaction_already_active", 111 | "25008": "held_cursor_requires_same_isolation_level", 112 | "25003": "inappropriate_access_mode_for_branch_transaction", 113 | "25004": "inappropriate_isolation_level_for_branch_transaction", 114 | "25005": "no_active_sql_transaction_for_branch_transaction", 115 | "25006": "read_only_sql_transaction", 116 | "25007": "schema_and_data_statement_mixing_not_supported", 117 | "25P01": "no_active_sql_transaction", 118 | "25P02": "in_failed_sql_transaction", 119 | "25P03": "idle_in_transaction_session_timeout", 120 | "26000": "invalid_sql_statement_name", 121 | "27000": "triggered_data_change_violation", 122 | "28000": "invalid_authorization_specification", 123 | "28P01": "invalid_password", 124 | "2B000": "dependent_privilege_descriptors_still_exist", 125 | "2BP01": "dependent_objects_still_exist", 126 | "2D000": "invalid_transaction_termination", 127 | "2F000": "sql_routine_exception", 128 | "2F005": "function_executed_no_return_statement", 129 | "2F002": "modifying_sql_data_not_permitted", 130 | "2F003": "prohibited_sql_statement_attempted", 131 | "2F004": "reading_sql_data_not_permitted", 132 | "34000": "invalid_cursor_name", 133 | "38000": "external_routine_exception", 134 | "38001": "containing_sql_not_permitted", 135 | "38002": "modifying_sql_data_not_permitted", 136 | "38003": "prohibited_sql_statement_attempted", 137 | "38004": "reading_sql_data_not_permitted", 138 | "39000": "external_routine_invocation_exception", 139 | "39001": "invalid_sqlstate_returned", 140 | "39004": "null_value_not_allowed", 141 | "39P01": "trigger_protocol_violated", 142 | "39P02": "srf_protocol_violated", 143 | "39P03": "event_trigger_protocol_violated", 144 | "3B000": "savepoint_exception", 145 | "3B001": "invalid_savepoint_specification", 146 | "3D000": "invalid_catalog_name", 147 | "3F000": "invalid_schema_name", 148 | "40000": "transaction_rollback", 149 | "40002": "transaction_integrity_constraint_violation", 150 | "40001": "serialization_failure", 151 | "40003": "statement_completion_unknown", 152 | "40P01": "deadlock_detected", 153 | "42000": "syntax_error_or_access_rule_violation", 154 | "42601": "syntax_error", 155 | "42501": "insufficient_privilege", 156 | "42846": "cannot_coerce", 157 | "42803": "grouping_error", 158 | "42P20": "windowing_error", 159 | "42P19": "invalid_recursion", 160 | "42830": "invalid_foreign_key", 161 | "42602": "invalid_name", 162 | "42622": "name_too_long", 163 | "42939": "reserved_name", 164 | "42804": "datatype_mismatch", 165 | "42P18": "indeterminate_datatype", 166 | "42P21": "collation_mismatch", 167 | "42P22": "indeterminate_collation", 168 | "42809": "wrong_object_type", 169 | "428C9": "generated_always", 170 | "42703": "undefined_column", 171 | "42883": "undefined_function", 172 | "42P01": "undefined_table", 173 | "42P02": "undefined_parameter", 174 | "42704": "undefined_object", 175 | "42701": "duplicate_column", 176 | "42P03": "duplicate_cursor", 177 | "42P04": "duplicate_database", 178 | "42723": "duplicate_function", 179 | "42P05": "duplicate_prepared_statement", 180 | "42P06": "duplicate_schema", 181 | "42P07": "duplicate_table", 182 | "42712": "duplicate_alias", 183 | "42710": "duplicate_object", 184 | "42702": "ambiguous_column", 185 | "42725": "ambiguous_function", 186 | "42P08": "ambiguous_parameter", 187 | "42P09": "ambiguous_alias", 188 | "42P10": "invalid_column_reference", 189 | "42611": "invalid_column_definition", 190 | "42P11": "invalid_cursor_definition", 191 | "42P12": "invalid_database_definition", 192 | "42P13": "invalid_function_definition", 193 | "42P14": "invalid_prepared_statement_definition", 194 | "42P15": "invalid_schema_definition", 195 | "42P16": "invalid_table_definition", 196 | "42P17": "invalid_object_definition", 197 | "44000": "with_check_option_violation", 198 | "53000": "insufficient_resources", 199 | "53100": "disk_full", 200 | "53200": "out_of_memory", 201 | "53300": "too_many_connections", 202 | "53400": "configuration_limit_exceeded", 203 | "54000": "program_limit_exceeded", 204 | "54001": "statement_too_complex", 205 | "54011": "too_many_columns", 206 | "54023": "too_many_arguments", 207 | "55000": "object_not_in_prerequisite_state", 208 | "55006": "object_in_use", 209 | "55P02": "cant_change_runtime_param", 210 | "55P03": "lock_not_available", 211 | "55P04": "unsafe_new_enum_value_usage", 212 | "57000": "operator_intervention", 213 | "57014": "query_canceled", 214 | "57P01": "admin_shutdown", 215 | "57P02": "crash_shutdown", 216 | "57P03": "cannot_connect_now", 217 | "57P04": "database_dropped", 218 | "58000": "system_error", 219 | "58030": "io_error", 220 | "58P01": "undefined_file", 221 | "58P02": "duplicate_file", 222 | "72000": "snapshot_too_old", 223 | "F0000": "config_file_error", 224 | "F0001": "lock_file_exists", 225 | "HV000": "fdw_error", 226 | "HV005": "fdw_column_name_not_found", 227 | "HV002": "fdw_dynamic_parameter_value_needed", 228 | "HV010": "fdw_function_sequence_error", 229 | "HV021": "fdw_inconsistent_descriptor_information", 230 | "HV024": "fdw_invalid_attribute_value", 231 | "HV007": "fdw_invalid_column_name", 232 | "HV008": "fdw_invalid_column_number", 233 | "HV004": "fdw_invalid_data_type", 234 | "HV006": "fdw_invalid_data_type_descriptors", 235 | "HV091": "fdw_invalid_descriptor_field_identifier", 236 | "HV00B": "fdw_invalid_handle", 237 | "HV00C": "fdw_invalid_option_index", 238 | "HV00D": "fdw_invalid_option_name", 239 | "HV090": "fdw_invalid_string_length_or_buffer_length", 240 | "HV00A": "fdw_invalid_string_format", 241 | "HV009": "fdw_invalid_use_of_null_pointer", 242 | "HV014": "fdw_too_many_handles", 243 | "HV001": "fdw_out_of_memory", 244 | "HV00P": "fdw_no_schemas", 245 | "HV00J": "fdw_option_name_not_found", 246 | "HV00K": "fdw_reply_handle", 247 | "HV00Q": "fdw_schema_not_found", 248 | "HV00R": "fdw_table_not_found", 249 | "HV00L": "fdw_unable_to_create_execution", 250 | "HV00M": "fdw_unable_to_create_reply", 251 | "HV00N": "fdw_unable_to_establish_connection", 252 | "P0000": "plpgsql_error", 253 | "P0001": "raise_exception", 254 | "P0002": "no_data_found", 255 | "P0003": "too_many_rows", 256 | "P0004": "assert_failure", 257 | "XX000": "internal_error", 258 | "XX001": "data_corrupted", 259 | "XX002": "index_corrupted" 260 | } 261 | -------------------------------------------------------------------------------- /errors_to_json.sed: -------------------------------------------------------------------------------- 1 | 1i\ 2 | { 3 | 4 | /^#/d 5 | /^\s*$/d 6 | /^Section:/d 7 | 8 | # There are duplicate macros for some error codes. Fortunately without the 9 | # spec_name. 10 | /^[[:alnum:]_]+ +[[:alnum:]_]+ +[[:alnum:]_]+$/d 11 | 12 | s/^([[:alnum:]_]+) +([[:alnum:]_]+) +([[:alnum:]_]+) +([[:alnum:]_]+)$/ "\1": "\4"/ 13 | $!s/$/,/ 14 | 15 | $a\ 16 | } 17 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var StandardError = require("standard-error") 2 | var ERRORS = require("./errors") 3 | var indexOf = Array.prototype.indexOf 4 | module.exports = PgError 5 | 6 | function PgError(fields) { 7 | if (fields == null) fields = Object 8 | 9 | StandardError.call(this, fields.M, { 10 | // Perhaps severity should be lowercased at one point to be consistent with 11 | // condition. But that would differ from node-pg's default behavior. 12 | severity: fields.S, 13 | code: fields.C, 14 | condition: ERRORS[fields.C], 15 | detail: fields.D, 16 | hint: fields.H, 17 | position: fields.P && Number(fields.P), 18 | internalPosition: fields.p && Number(fields.p), 19 | internalQuery: fields.q, 20 | where: fields.W, 21 | schema: fields.s, 22 | table: fields.t, 23 | column: fields.c, 24 | dataType: fields.d, 25 | constraint: fields.n, 26 | file: fields.F, 27 | line: fields.L && Number(fields.L), 28 | routine: fields.R 29 | }) 30 | } 31 | 32 | PgError.prototype = Object.create(StandardError.prototype, { 33 | constructor: {value: PgError, configurable: true, writeable: true} 34 | }) 35 | 36 | PgError.parse = function(buffer) { 37 | return new PgError(parse(buffer)) 38 | } 39 | 40 | // http://www.postgresql.org/docs/9.3/static/protocol-message-types.html 41 | // http://www.postgresql.org/docs/9.3/static/protocol-message-formats.html 42 | // Not yet sure of the encoding of those strings... 43 | function parse(buffer) { 44 | var p = 0, type, fields = {} 45 | 46 | // This could've been written with String.prototype.split as well. 47 | // I wonder which is faster. Does it really matter though? Who has that many 48 | // errors anyway. 49 | while ((type = String.fromCharCode(buffer[p++])) != "\0") { 50 | fields[type] = buffer.toString("utf8", p, p = indexOf.call(buffer, 0, p)) 51 | ;++p 52 | } 53 | 54 | return fields 55 | } 56 | -------------------------------------------------------------------------------- /npm-shrinkwrap.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg-error", 3 | "version": "1.0.0", 4 | "dependencies": { 5 | "co-mocha": { 6 | "version": "1.1.0", 7 | "from": "co-mocha@>=1.1.0 <2.0.0", 8 | "resolved": "https://registry.npmjs.org/co-mocha/-/co-mocha-1.1.0.tgz", 9 | "dependencies": { 10 | "co": { 11 | "version": "4.0.1", 12 | "from": "co@>=4.0.0 <5.0.0", 13 | "resolved": "https://registry.npmjs.org/co/-/co-4.0.1.tgz" 14 | }, 15 | "is-generator": { 16 | "version": "1.0.0", 17 | "from": "is-generator@>=1.0.0 <2.0.0", 18 | "resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.0.tgz" 19 | } 20 | } 21 | }, 22 | "mocha": { 23 | "version": "1.21.5", 24 | "from": "mocha@>=1.18.2 <2.0.0", 25 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-1.21.5.tgz", 26 | "dependencies": { 27 | "commander": { 28 | "version": "2.3.0", 29 | "from": "commander@2.3.0", 30 | "resolved": "https://registry.npmjs.org/commander/-/commander-2.3.0.tgz" 31 | }, 32 | "debug": { 33 | "version": "2.0.0", 34 | "from": "debug@2.0.0", 35 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.0.0.tgz", 36 | "dependencies": { 37 | "ms": { 38 | "version": "0.6.2", 39 | "from": "ms@0.6.2", 40 | "resolved": "https://registry.npmjs.org/ms/-/ms-0.6.2.tgz" 41 | } 42 | } 43 | }, 44 | "diff": { 45 | "version": "1.0.8", 46 | "from": "diff@1.0.8", 47 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz" 48 | }, 49 | "escape-string-regexp": { 50 | "version": "1.0.2", 51 | "from": "escape-string-regexp@1.0.2", 52 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz" 53 | }, 54 | "glob": { 55 | "version": "3.2.3", 56 | "from": "glob@3.2.3", 57 | "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.3.tgz", 58 | "dependencies": { 59 | "minimatch": { 60 | "version": "0.2.14", 61 | "from": "minimatch@>=0.2.11 <0.3.0", 62 | "dependencies": { 63 | "lru-cache": { 64 | "version": "2.5.0", 65 | "from": "lru-cache@>=2.0.0 <3.0.0" 66 | }, 67 | "sigmund": { 68 | "version": "1.0.0", 69 | "from": "sigmund@>=1.0.0 <1.1.0" 70 | } 71 | } 72 | }, 73 | "graceful-fs": { 74 | "version": "2.0.3", 75 | "from": "graceful-fs@>=2.0.0 <2.1.0" 76 | }, 77 | "inherits": { 78 | "version": "2.0.1", 79 | "from": "inherits@>=2.0.0 <3.0.0" 80 | } 81 | } 82 | }, 83 | "growl": { 84 | "version": "1.8.1", 85 | "from": "growl@1.8.1", 86 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.8.1.tgz" 87 | }, 88 | "jade": { 89 | "version": "0.26.3", 90 | "from": "jade@0.26.3", 91 | "resolved": "https://registry.npmjs.org/jade/-/jade-0.26.3.tgz", 92 | "dependencies": { 93 | "commander": { 94 | "version": "0.6.1", 95 | "from": "commander@0.6.1" 96 | }, 97 | "mkdirp": { 98 | "version": "0.3.0", 99 | "from": "mkdirp@0.3.0" 100 | } 101 | } 102 | }, 103 | "mkdirp": { 104 | "version": "0.5.0", 105 | "from": "mkdirp@0.5.0", 106 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz", 107 | "dependencies": { 108 | "minimist": { 109 | "version": "0.0.8", 110 | "from": "minimist@0.0.8", 111 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz" 112 | } 113 | } 114 | } 115 | } 116 | }, 117 | "must": { 118 | "version": "0.13.4", 119 | "from": "must@latest", 120 | "resolved": "https://registry.npmjs.org/must/-/must-0.13.4.tgz", 121 | "dependencies": { 122 | "kindof": { 123 | "version": "2.0.0", 124 | "from": "kindof@>=2.0.0 <3.0.0", 125 | "resolved": "https://registry.npmjs.org/kindof/-/kindof-2.0.0.tgz" 126 | }, 127 | "egal": { 128 | "version": "1.3.0", 129 | "from": "egal@>=1.3.0 <2.0.0", 130 | "resolved": "https://registry.npmjs.org/egal/-/egal-1.3.0.tgz" 131 | }, 132 | "oolong": { 133 | "version": "1.15.1", 134 | "from": "oolong@>=1.11.0 <2.0.0", 135 | "resolved": "https://registry.npmjs.org/oolong/-/oolong-1.15.1.tgz" 136 | }, 137 | "lodash.wrap": { 138 | "version": "3.0.1", 139 | "from": "lodash.wrap@>=3.0.0 <4.0.0", 140 | "resolved": "https://registry.npmjs.org/lodash.wrap/-/lodash.wrap-3.0.1.tgz", 141 | "dependencies": { 142 | "lodash._createwrapper": { 143 | "version": "3.2.0", 144 | "from": "lodash._createwrapper@>=3.0.0 <4.0.0", 145 | "resolved": "https://registry.npmjs.org/lodash._createwrapper/-/lodash._createwrapper-3.2.0.tgz", 146 | "dependencies": { 147 | "lodash._root": { 148 | "version": "3.0.1", 149 | "from": "lodash._root@>=3.0.0 <4.0.0", 150 | "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz" 151 | } 152 | } 153 | } 154 | } 155 | }, 156 | "json-stringify-safe": { 157 | "version": "5.0.1", 158 | "from": "json-stringify-safe@>=5.0.0 <6.0.0", 159 | "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz" 160 | } 161 | } 162 | }, 163 | "pg": { 164 | "version": "6.1.2", 165 | "from": "https://registry.npmjs.org/pg/-/pg-6.1.2.tgz", 166 | "resolved": "https://registry.npmjs.org/pg/-/pg-6.1.2.tgz", 167 | "dependencies": { 168 | "buffer-writer": { 169 | "version": "1.0.1", 170 | "from": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz", 171 | "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-1.0.1.tgz" 172 | }, 173 | "packet-reader": { 174 | "version": "0.2.0", 175 | "from": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz", 176 | "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-0.2.0.tgz" 177 | }, 178 | "pg-connection-string": { 179 | "version": "0.1.3", 180 | "from": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz", 181 | "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-0.1.3.tgz" 182 | }, 183 | "pg-pool": { 184 | "version": "1.6.0", 185 | "from": "https://registry.npmjs.org/pg-pool/-/pg-pool-1.6.0.tgz", 186 | "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-1.6.0.tgz", 187 | "dependencies": { 188 | "generic-pool": { 189 | "version": "2.4.2", 190 | "from": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz", 191 | "resolved": "https://registry.npmjs.org/generic-pool/-/generic-pool-2.4.2.tgz" 192 | }, 193 | "object-assign": { 194 | "version": "4.1.0", 195 | "from": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz", 196 | "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.0.tgz" 197 | } 198 | } 199 | }, 200 | "pg-types": { 201 | "version": "1.11.0", 202 | "from": "https://registry.npmjs.org/pg-types/-/pg-types-1.11.0.tgz", 203 | "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-1.11.0.tgz", 204 | "dependencies": { 205 | "ap": { 206 | "version": "0.2.0", 207 | "from": "https://registry.npmjs.org/ap/-/ap-0.2.0.tgz", 208 | "resolved": "https://registry.npmjs.org/ap/-/ap-0.2.0.tgz" 209 | }, 210 | "postgres-array": { 211 | "version": "1.0.2", 212 | "from": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz", 213 | "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-1.0.2.tgz" 214 | }, 215 | "postgres-bytea": { 216 | "version": "1.0.0", 217 | "from": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz", 218 | "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz" 219 | }, 220 | "postgres-date": { 221 | "version": "1.0.3", 222 | "from": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz", 223 | "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.3.tgz" 224 | }, 225 | "postgres-interval": { 226 | "version": "1.0.2", 227 | "from": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.0.2.tgz", 228 | "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.0.2.tgz", 229 | "dependencies": { 230 | "xtend": { 231 | "version": "4.0.1", 232 | "from": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", 233 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz" 234 | } 235 | } 236 | } 237 | } 238 | }, 239 | "pgpass": { 240 | "version": "1.0.1", 241 | "from": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.1.tgz", 242 | "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.1.tgz", 243 | "dependencies": { 244 | "split": { 245 | "version": "1.0.0", 246 | "from": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", 247 | "resolved": "https://registry.npmjs.org/split/-/split-1.0.0.tgz", 248 | "dependencies": { 249 | "through": { 250 | "version": "2.3.4", 251 | "from": "https://registry.npmjs.org/through/-/through-2.3.4.tgz", 252 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.4.tgz" 253 | } 254 | } 255 | } 256 | } 257 | }, 258 | "semver": { 259 | "version": "4.3.2", 260 | "from": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz", 261 | "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.2.tgz" 262 | } 263 | } 264 | }, 265 | "standard-error": { 266 | "version": "1.1.0", 267 | "from": "standard-error@>=1.1.0 <2.0.0" 268 | } 269 | } 270 | } 271 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "pg-error", 3 | "version": "1.1.0", 4 | "description": "Error class that parses PostgreSQL's ErrorResponse format and sets human readable field names. Works with node-pg, too.", 5 | "keywords": [ 6 | "error", 7 | "exception", 8 | "pg", 9 | "postgres", 10 | "postgresql" 11 | ], 12 | "homepage": "https://github.com/moll/node-pg-error", 13 | "bugs": "https://github.com/moll/node-pg-error/issues", 14 | 15 | "author": { 16 | "name": "Andri Möll", 17 | "email": "andri@dot.ee", 18 | "url": "http://themoll.com" 19 | }, 20 | 21 | "repository": { 22 | "type": "git", 23 | "url": "git://github.com/moll/node-pg-error.git" 24 | }, 25 | 26 | "licenses": [{ 27 | "type": "LAGPL", 28 | "url": "https://github.com/moll/node-pg-error/blob/master/LICENSE" 29 | }], 30 | 31 | "main": "index.js", 32 | "scripts": {"test": "make test"}, 33 | 34 | "dependencies": { 35 | "standard-error": ">= 1.1.0 < 2" 36 | }, 37 | 38 | "devDependencies": { 39 | "mocha": ">= 1.18.2 < 2", 40 | "co-mocha": ">= 1.1.0 < 2", 41 | "must": ">= 0.13.0 < 0.14", 42 | "pg": ">= 4.1.1 < 7" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /test/index_test.js: -------------------------------------------------------------------------------- 1 | var PgError = require("..") 2 | 3 | var Pg = require("pg") 4 | Pg.Connection.prototype.parseE = PgError.parse 5 | Pg.Connection.prototype.parseN = PgError.parse 6 | 7 | var pg = new Pg.Client({database: "pg_error_test"}) 8 | 9 | pg.connection.on("PgError", function(err) { 10 | switch (err.severity) { 11 | case "ERROR": 12 | case "FATAL": 13 | case "PANIC": return this.emit("error", err) 14 | default: return this.emit("notice", err) 15 | } 16 | }) 17 | 18 | describe("PgError", function() { 19 | describe("new", function() { 20 | it("must be an instance of PgError", function() { 21 | new PgError().must.be.an.instanceof(PgError) 22 | }) 23 | 24 | it("must be an instance of Error", function() { 25 | new PgError().must.be.an.instanceof(Error) 26 | }) 27 | 28 | it("must assign all fields to names", function() { 29 | var err = new PgError({ 30 | M: "null value in column \"name\" violates not-null constraint", 31 | S: "ERROR", 32 | C: "23502", 33 | D: "Failing row contains (1, null).", 34 | H: "Don't put nulls, girl.", 35 | P: "3", 36 | p: "5", 37 | q: "INSERT INTO", 38 | W: "Here.\nAnd there.", 39 | s: "public", 40 | t: "models", 41 | c: "name", 42 | d: "NULL", 43 | n: "no_nulls_allowed", 44 | F: "execMain.c", 45 | L: "1611", 46 | R: "ExecConstraints" 47 | }) 48 | 49 | var msg = "null value in column \"name\" violates not-null constraint" 50 | err.message.must.equal(msg) 51 | err.severity.must.equal("ERROR") 52 | err.code.must.equal("23502") 53 | err.detail.must.equal("Failing row contains (1, null).") 54 | err.hint.must.equal("Don't put nulls, girl.") 55 | err.position.must.equal(3) 56 | err.internalPosition.must.equal(5) 57 | err.internalQuery.must.equal("INSERT INTO") 58 | err.where.must.equal("Here.\nAnd there.") 59 | err.schema.must.equal("public") 60 | err.table.must.equal("models") 61 | err.column.must.equal("name") 62 | err.dataType.must.equal("NULL") 63 | err.constraint.must.equal("no_nulls_allowed") 64 | err.file.must.equal("execMain.c") 65 | err.line.must.equal(1611) 66 | err.routine.must.equal("ExecConstraints") 67 | }) 68 | 69 | it("must set condition based on code", function() { 70 | new PgError({C: "23502"}).condition.must.equal("not_null_violation") 71 | }) 72 | 73 | it("must parse code as string", function() { 74 | new PgError({C: "42P17"}).code.must.equal("42P17") 75 | }) 76 | 77 | it("must parse line to number", function() { 78 | new PgError({L: "1337"}).line.must.equal(1337) 79 | }) 80 | 81 | it("must leave line as undefined if not given", function() { 82 | new PgError().must.have.property("line", undefined) 83 | }) 84 | 85 | it("must parse position to number", function() { 86 | new PgError({P: "1337"}).position.must.equal(1337) 87 | }) 88 | 89 | it("must leave position as undefined if not given", function() { 90 | new PgError().must.have.property("position", undefined) 91 | }) 92 | 93 | it("must parse internal position to number", function() { 94 | new PgError({p: "1337"}).internalPosition.must.equal(1337) 95 | }) 96 | 97 | it("must leave internal position as undefined if not given", function() { 98 | new PgError().must.have.property("internalPosition", undefined) 99 | }) 100 | }) 101 | 102 | describe(".parse", function() { 103 | it("must parse a null separated buffer of fields", function() { 104 | var data = "" 105 | data += "Mnull value in column \"name\" violates not-null constraint\0" 106 | data += "SERROR\0" 107 | data += "C23502\0" 108 | data += "DFailing row contains (1, null).\0" 109 | data += "spublic\0" 110 | data += "tmodels\0" 111 | data += "cname\0" 112 | data += "FexecMain.c\0" 113 | data += "L1611\0" 114 | data += "RExecConstraints\0" 115 | 116 | var err = PgError.parse(new Buffer(data + "\0")) 117 | err.must.be.an.instanceof(PgError) 118 | 119 | var msg = "null value in column \"name\" violates not-null constraint" 120 | err.message.must.equal(msg) 121 | err.severity.must.equal("ERROR") 122 | err.code.must.equal("23502") 123 | err.detail.must.equal("Failing row contains (1, null).") 124 | err.schema.must.equal("public") 125 | err.table.must.equal("models") 126 | err.column.must.equal("name") 127 | err.file.must.equal("execMain.c") 128 | err.line.must.equal(1611) 129 | err.routine.must.equal("ExecConstraints") 130 | }) 131 | }) 132 | }) 133 | 134 | describe("Pg.Connection", function() { 135 | before(function(done) { pg.connect(done) }) 136 | before(function(done) { pg.query("SET client_min_messages TO DEBUG", done) }) 137 | after(function() { pg.end() }) 138 | 139 | beforeEach(function(done) { pg.query("BEGIN", done) }) 140 | afterEach(function(done) { pg.query("ROLLBACK", done) }) 141 | 142 | it("must emit query errors as PgError", function*() { 143 | yield pg.query.bind(pg, ` 144 | CREATE TEMPORARY TABLE "models" ( 145 | "serial" SERIAL, 146 | "name" TEXT NOT NULL 147 | ) 148 | `) 149 | 150 | var err 151 | try { yield pg.query.bind(pg, 'INSERT INTO "models" DEFAULT VALUES') } 152 | catch (ex) { err = ex } 153 | 154 | err.must.be.an.instanceof(PgError) 155 | err.severity.must.equal("ERROR") 156 | err.condition.must.equal("not_null_violation") 157 | err.table.must.equal("models") 158 | err.column.must.equal("name") 159 | }) 160 | 161 | it("must emit query syntax errors as PgError", function*() { 162 | var err 163 | try { yield pg.query.bind(pg, "FOO INTO BAR") } 164 | catch (ex) { err = ex } 165 | 166 | err.must.be.an.instanceof(PgError) 167 | err.severity.must.equal("ERROR") 168 | err.condition.must.equal("syntax_error") 169 | }) 170 | 171 | it("must emit EXCEPTION as an error", function*() { 172 | var err 173 | try { 174 | yield pg.query.bind(pg, ` 175 | DO language plpgsql $$ 176 | BEGIN 177 | RAISE EXCEPTION 'Pay attention!'; 178 | END 179 | $$ 180 | `) 181 | } catch (ex) { err = ex } 182 | 183 | err.must.be.an.error(PgError) 184 | err.message.must.equal("Pay attention!") 185 | err.severity.must.equal("ERROR") 186 | }) 187 | 188 | ;["WARNING", "NOTICE", "INFO", "LOG", "DEBUG"].forEach(function(severity) { 189 | it("must emit " + severity + " as a notice", function*() { 190 | var notice; pg.once("notice", function(msg) { notice = msg }) 191 | 192 | yield pg.query.bind(pg, ` 193 | DO language plpgsql $$ 194 | BEGIN 195 | RAISE ${severity} 'Pay attention!'; 196 | END 197 | $$ 198 | `) 199 | 200 | notice.must.be.an.instanceof(PgError) 201 | notice.message.must.equal("Pay attention!") 202 | notice.severity.must.equal(severity) 203 | }) 204 | }) 205 | }) 206 | -------------------------------------------------------------------------------- /test/mocha.opts: -------------------------------------------------------------------------------- 1 | --recursive 2 | --check-leaks 3 | --require must 4 | --require co-mocha 5 | --------------------------------------------------------------------------------