summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS2
-rw-r--r--doc/src/errors.rst60
-rw-r--r--doc/src/extensions.rst7
-rw-r--r--doc/src/index.rst1
-rw-r--r--doc/src/module.rst17
-rw-r--r--lib/errors.py1539
-rw-r--r--psycopg/pqpath.c44
-rwxr-xr-xscripts/make_errorcodes.py62
-rwxr-xr-xscripts/make_errors.py210
-rwxr-xr-xtests/__init__.py2
-rwxr-xr-xtests/test_errors.py70
11 files changed, 1952 insertions, 62 deletions
diff --git a/NEWS b/NEWS
index ed26a44..6094994 100644
--- a/NEWS
+++ b/NEWS
@@ -6,6 +6,8 @@ What's new in psycopg 2.8
New features:
+- Added `~psycopg2.errors` module. Every PostgreSQL error is converted into
+ a specific exception class (:ticket:`#682`).
- Added `~psycopg2.extensions.encrypt_password()` function (:ticket:`#576`).
- Added `~psycopg2.extensions.Column.table_oid` and
`~psycopg2.extensions.Column.table_column` attributes on `cursor.description`
diff --git a/doc/src/errors.rst b/doc/src/errors.rst
new file mode 100644
index 0000000..061999b
--- /dev/null
+++ b/doc/src/errors.rst
@@ -0,0 +1,60 @@
+`psycopg2.errors` -- Exception classes mapping PostgreSQL errors
+================================================================
+
+.. sectionauthor:: Daniele Varrazzo <daniele.varrazzo@gmail.com>
+
+.. index::
+ single: Error; Class
+
+.. module:: psycopg2.errors
+
+.. versionadded:: 2.8
+
+This module contains the classes psycopg raises upon receiving an error from
+the database with a :sql:`SQLSTATE` value attached. The module is generated
+from the PostgreSQL source code and includes classes for every error defined
+by PostgreSQL in versions between 9.1 and 11.
+
+Every class in the module is named after what referred as "condition name" `in
+the documentation`__, converted to CamelCase: e.g. the error 22012,
+``division_by_zero`` is exposed by this module as the class `!DivisionByZero`.
+
+.. __: https://www.postgresql.org/docs/current/static/errcodes-appendix.html#ERRCODES-TABLE
+
+Every exception class is a subclass of one of the :ref:`standard DB-API
+exception <dbapi-exceptions>` and expose the `~psycopg2.Error` interface.
+Each class' superclass is what used to be raised by psycopg in versions before
+the introduction of this module, so everything should be compatible with
+previously written code catching one the DB-API class: if your code used to
+catch `!IntegrityError` to detect a duplicate entry, it will keep on working
+even if a more specialised subclass such as `UniqueViolation` is raised.
+
+The new classes allow a more idiomatic way to check and process a specific
+error among the many the database may return. For instance, in order to check
+that a table is locked, the following code could have been used previously:
+
+.. code-block:: python
+
+ try:
+ cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
+ except psycopg2.OperationalError as e:
+ if e.pgcode == psycopg2.errorcodes.LOCK_NOT_AVAILABLE:
+ locked = True
+ else:
+ raise
+
+While this method is still available, the specialised class allows for a more
+idiomatic error handler:
+
+.. code-block:: python
+
+ try:
+ cur.execute("LOCK TABLE mytable IN ACCESS EXCLUSIVE MODE NOWAIT")
+ except psycopg2.errors.LockNotAvailable:
+ locked = True
+
+For completeness, the module also exposes all the DB-API-defined classes and
+:ref:`a few psycopg-specific exceptions <extension-exceptions>` previously
+exposed by the `!extensions` module. One stop shop for all your mistakes...
+
+.. autofunction:: lookup
diff --git a/doc/src/extensions.rst b/doc/src/extensions.rst
index 159774b..f331721 100644
--- a/doc/src/extensions.rst
+++ b/doc/src/extensions.rst
@@ -509,12 +509,19 @@ details.
.. index::
single: Exceptions; Additional
+.. _extension-exceptions:
+
Additional exceptions
---------------------
The module exports a few exceptions in addition to the :ref:`standard ones
<dbapi-exceptions>` defined by the |DBAPI|_.
+.. note::
+ From psycopg 2.8 these error classes are also exposed by the
+ `psycopg2.errors` module.
+
+
.. exception:: QueryCanceledError
(subclasses `~psycopg2.OperationalError`)
diff --git a/doc/src/index.rst b/doc/src/index.rst
index ecf9c38..3f2e401 100644
--- a/doc/src/index.rst
+++ b/doc/src/index.rst
@@ -42,6 +42,7 @@ Psycopg 2 is both Unicode and Python 3 friendly.
advanced
extensions
extras
+ errors
sql
tz
pool
diff --git a/doc/src/module.rst b/doc/src/module.rst
index 034c7bb..6268962 100644
--- a/doc/src/module.rst
+++ b/doc/src/module.rst
@@ -250,13 +250,14 @@ available through the following exceptions:
.. extension::
- Psycopg may raise a few other, more specialized, exceptions: currently
- `~psycopg2.extensions.QueryCanceledError` and
- `~psycopg2.extensions.TransactionRollbackError` are defined. These
- exceptions are not exposed by the main `!psycopg2` module but are
- made available by the `~psycopg2.extensions` module. All the
- additional exceptions are subclasses of standard |DBAPI| exceptions, so
- trapping them specifically is not required.
+ Psycopg actually raises a different exception for each :sql:`SQLSTATE`
+ error returned by the database: the classes are available in the
+ `psycopg2.errors` module. Every exception class is a subclass of one of
+ the exception classes defined here though, so they don't need to be
+ trapped specifically: trapping `!Error` or `!DatabaseError` is usually
+ what needed to write a generic error handler; trapping a specific error
+ such as `!NotNullViolation` can be useful to write specific exception
+ handlers.
This is the exception inheritance layout:
@@ -270,8 +271,6 @@ This is the exception inheritance layout:
\|__ `DatabaseError`
\|__ `DataError`
\|__ `OperationalError`
- \| \|__ `psycopg2.extensions.QueryCanceledError`
- \| \|__ `psycopg2.extensions.TransactionRollbackError`
\|__ `IntegrityError`
\|__ `InternalError`
\|__ `ProgrammingError`
diff --git a/lib/errors.py b/lib/errors.py
new file mode 100644
index 0000000..492d5aa
--- /dev/null
+++ b/lib/errors.py
@@ -0,0 +1,1539 @@
+"""Error classes for PostgreSQL error codes
+"""
+
+# Imported for completeness, but unused in this module.
+from psycopg2._psycopg import Error, Warning, InterfaceError # noqa
+
+from psycopg2._psycopg import (
+ DataError, DatabaseError, ProgrammingError, IntegrityError,
+ InternalError, NotSupportedError, OperationalError,
+ QueryCanceledError, TransactionRollbackError)
+
+
+def lookup(code):
+ """Lookup an error code and return its exception class.
+
+ Raise `!KeyError` if the code is not found.
+ """
+ return _by_sqlstate[code]
+
+
+_by_sqlstate = {}
+
+
+# autogenerated data: do not edit below this point.
+
+
+# Class 02 - No Data (this is also a warning class per the SQL standard)
+
+
+class NoData(DatabaseError):
+ pass
+
+_by_sqlstate['02000'] = NoData
+
+
+class NoAdditionalDynamicResultSetsReturned(DatabaseError):
+ pass
+
+_by_sqlstate['02001'] = NoAdditionalDynamicResultSetsReturned
+
+
+# Class 03 - SQL Statement Not Yet Complete
+
+
+class SqlStatementNotYetComplete(DatabaseError):
+ pass
+
+_by_sqlstate['03000'] = SqlStatementNotYetComplete
+
+
+# Class 08 - Connection Exception
+
+
+class ConnectionException(DatabaseError):
+ pass
+
+_by_sqlstate['08000'] = ConnectionException
+
+
+class SqlclientUnableToEstablishSqlconnection(DatabaseError):
+ pass
+
+_by_sqlstate['08001'] = SqlclientUnableToEstablishSqlconnection
+
+
+class ConnectionDoesNotExist(DatabaseError):
+ pass
+
+_by_sqlstate['08003'] = ConnectionDoesNotExist
+
+
+class SqlserverRejectedEstablishmentOfSqlconnection(DatabaseError):
+ pass
+
+_by_sqlstate['08004'] = SqlserverRejectedEstablishmentOfSqlconnection
+
+
+class ConnectionFailure(DatabaseError):
+ pass
+
+_by_sqlstate['08006'] = ConnectionFailure
+
+
+class TransactionResolutionUnknown(DatabaseError):
+ pass
+
+_by_sqlstate['08007'] = TransactionResolutionUnknown
+
+
+class ProtocolViolation(DatabaseError):
+ pass
+
+_by_sqlstate['08P01'] = ProtocolViolation
+
+
+# Class 09 - Triggered Action Exception
+
+
+class TriggeredActionException(DatabaseError):
+ pass
+
+_by_sqlstate['09000'] = TriggeredActionException
+
+
+# Class 0A - Feature Not Supported
+
+
+class FeatureNotSupported(NotSupportedError):
+ pass
+
+_by_sqlstate['0A000'] = FeatureNotSupported
+
+
+# Class 0B - Invalid Transaction Initiation
+
+
+class InvalidTransactionInitiation(DatabaseError):
+ pass
+
+_by_sqlstate['0B000'] = InvalidTransactionInitiation
+
+
+# Class 0F - Locator Exception
+
+
+class LocatorException(DatabaseError):
+ pass
+
+_by_sqlstate['0F000'] = LocatorException
+
+
+class InvalidLocatorSpecification(DatabaseError):
+ pass
+
+_by_sqlstate['0F001'] = InvalidLocatorSpecification
+
+
+# Class 0L - Invalid Grantor
+
+
+class InvalidGrantor(DatabaseError):
+ pass
+
+_by_sqlstate['0L000'] = InvalidGrantor
+
+
+class InvalidGrantOperation(DatabaseError):
+ pass
+
+_by_sqlstate['0LP01'] = InvalidGrantOperation
+
+
+# Class 0P - Invalid Role Specification
+
+
+class InvalidRoleSpecification(DatabaseError):
+ pass
+
+_by_sqlstate['0P000'] = InvalidRoleSpecification
+
+
+# Class 0Z - Diagnostics Exception
+
+
+class DiagnosticsException(DatabaseError):
+ pass
+
+_by_sqlstate['0Z000'] = DiagnosticsException
+
+
+class StackedDiagnosticsAccessedWithoutActiveHandler(DatabaseError):
+ pass
+
+_by_sqlstate['0Z002'] = StackedDiagnosticsAccessedWithoutActiveHandler
+
+
+# Class 20 - Case Not Found
+
+
+class CaseNotFound(ProgrammingError):
+ pass
+
+_by_sqlstate['20000'] = CaseNotFound
+
+
+# Class 21 - Cardinality Violation
+
+
+class CardinalityViolation(ProgrammingError):
+ pass
+
+_by_sqlstate['21000'] = CardinalityViolation
+
+
+# Class 22 - Data Exception
+
+
+class DataException(DataError):
+ pass
+
+_by_sqlstate['22000'] = DataException
+
+
+class StringDataRightTruncation(DataError):
+ pass
+
+_by_sqlstate['22001'] = StringDataRightTruncation
+
+
+class NullValueNoIndicatorParameter(DataError):
+ pass
+
+_by_sqlstate['22002'] = NullValueNoIndicatorParameter
+
+
+class NumericValueOutOfRange(DataError):
+ pass
+
+_by_sqlstate['22003'] = NumericValueOutOfRange
+
+
+class NullValueNotAllowed(DataError):
+ pass
+
+_by_sqlstate['22004'] = NullValueNotAllowed
+
+
+class ErrorInAssignment(DataError):
+ pass
+
+_by_sqlstate['22005'] = ErrorInAssignment
+
+
+class InvalidDatetimeFormat(DataError):
+ pass
+
+_by_sqlstate['22007'] = InvalidDatetimeFormat
+
+
+class DatetimeFieldOverflow(DataError):
+ pass
+
+_by_sqlstate['22008'] = DatetimeFieldOverflow
+
+
+class InvalidTimeZoneDisplacementValue(DataError):
+ pass
+
+_by_sqlstate['22009'] = InvalidTimeZoneDisplacementValue
+
+
+class EscapeCharacterConflict(DataError):
+ pass
+
+_by_sqlstate['2200B'] = EscapeCharacterConflict
+
+
+class InvalidUseOfEscapeCharacter(DataError):
+ pass
+
+_by_sqlstate['2200C'] = InvalidUseOfEscapeCharacter
+
+
+class InvalidEscapeOctet(DataError):
+ pass
+
+_by_sqlstate['2200D'] = InvalidEscapeOctet
+
+
+class ZeroLengthCharacterString(DataError):
+ pass
+
+_by_sqlstate['2200F'] = ZeroLengthCharacterString
+
+
+class MostSpecificTypeMismatch(DataError):
+ pass
+
+_by_sqlstate['2200G'] = MostSpecificTypeMismatch
+
+
+class SequenceGeneratorLimitExceeded(DataError):
+ pass
+
+_by_sqlstate['2200H'] = SequenceGeneratorLimitExceeded
+
+
+class NotAnXmlDocument(DataError):
+ pass
+
+_by_sqlstate['2200L'] = NotAnXmlDocument
+
+
+class InvalidXmlDocument(DataError):
+ pass
+
+_by_sqlstate['2200M'] = InvalidXmlDocument
+
+
+class InvalidXmlContent(DataError):
+ pass
+
+_by_sqlstate['2200N'] = InvalidXmlContent
+
+
+class InvalidXmlComment(DataError):
+ pass
+
+_by_sqlstate['2200S'] = InvalidXmlComment
+
+
+class InvalidXmlProcessingInstruction(DataError):
+ pass
+
+_by_sqlstate['2200T'] = InvalidXmlProcessingInstruction
+
+
+class InvalidIndicatorParameterValue(DataError):
+ pass
+
+_by_sqlstate['22010'] = InvalidIndicatorParameterValue
+
+
+class SubstringError(DataError):
+ pass
+
+_by_sqlstate['22011'] = SubstringError
+
+
+class DivisionByZero(DataError):
+ pass
+
+_by_sqlstate['22012'] = DivisionByZero
+
+
+class InvalidPrecedingOrFollowingSize(DataError):
+ pass
+
+_by_sqlstate['22013'] = InvalidPrecedingOrFollowingSize
+
+
+class InvalidArgumentForNtileFunction(DataError):
+ pass
+
+_by_sqlstate['22014'] = InvalidArgumentForNtileFunction
+
+
+class IntervalFieldOverflow(DataError):
+ pass
+
+_by_sqlstate['22015'] = IntervalFieldOverflow
+
+
+class InvalidArgumentForNthValueFunction(DataError):
+ pass
+
+_by_sqlstate['22016'] = InvalidArgumentForNthValueFunction
+
+
+class InvalidCharacterValueForCast(DataError):
+ pass
+
+_by_sqlstate['22018'] = InvalidCharacterValueForCast
+
+
+class InvalidEscapeCharacter(DataError):
+ pass
+
+_by_sqlstate['22019'] = InvalidEscapeCharacter
+
+
+class InvalidRegularExpression(DataError):
+ pass
+
+_by_sqlstate['2201B'] = InvalidRegularExpression
+
+
+class InvalidArgumentForLogarithm(DataError):
+ pass
+
+_by_sqlstate['2201E'] = InvalidArgumentForLogarithm
+
+
+class InvalidArgumentForPowerFunction(DataError):
+ pass
+
+_by_sqlstate['2201F'] = InvalidArgumentForPowerFunction
+
+
+class InvalidArgumentForWidthBucketFunction(DataError):
+ pass
+
+_by_sqlstate['2201G'] = InvalidArgumentForWidthBucketFunction
+
+
+class InvalidRowCountInLimitClause(DataError):
+ pass
+
+_by_sqlstate['2201W'] = InvalidRowCountInLimitClause
+
+
+class InvalidRowCountInResultOffsetClause(DataError):
+ pass
+
+_by_sqlstate['2201X'] = InvalidRowCountInResultOffsetClause
+
+
+class CharacterNotInRepertoire(DataError):
+ pass
+
+_by_sqlstate['22021'] = CharacterNotInRepertoire
+
+
+class IndicatorOverflow(DataError):
+ pass
+
+_by_sqlstate['22022'] = IndicatorOverflow
+
+
+class InvalidParameterValue(DataError):
+ pass
+
+_by_sqlstate['22023'] = InvalidParameterValue
+
+
+class UnterminatedCString(DataError):
+ pass
+
+_by_sqlstate['22024'] = UnterminatedCString
+
+
+class InvalidEscapeSequence(DataError):
+ pass
+
+_by_sqlstate['22025'] = InvalidEscapeSequence
+
+
+class StringDataLengthMismatch(DataError):
+ pass
+
+_by_sqlstate['22026'] = StringDataLengthMismatch
+
+
+class TrimError(DataError):
+ pass
+
+_by_sqlstate['22027'] = TrimError
+
+
+class ArraySubscriptError(DataError):
+ pass
+
+_by_sqlstate['2202E'] = ArraySubscriptError
+
+
+class InvalidTablesampleRepeat(DataError):
+ pass
+
+_by_sqlstate['2202G'] = InvalidTablesampleRepeat
+
+
+class InvalidTablesampleArgument(DataError):
+ pass
+
+_by_sqlstate['2202H'] = InvalidTablesampleArgument
+
+
+class FloatingPointException(DataError):
+ pass
+
+_by_sqlstate['22P01'] = FloatingPointException
+
+
+class InvalidTextRepresentation(DataError):
+ pass
+
+_by_sqlstate['22P02'] = InvalidTextRepresentation
+
+
+class InvalidBinaryRepresentation(DataError):
+ pass
+
+_by_sqlstate['22P03'] = InvalidBinaryRepresentation
+
+
+class BadCopyFileFormat(DataError):
+ pass
+
+_by_sqlstate['22P04'] = BadCopyFileFormat
+
+
+class UntranslatableCharacter(DataError):
+ pass
+
+_by_sqlstate['22P05'] = UntranslatableCharacter
+
+
+class NonstandardUseOfEscapeCharacter(DataError):
+ pass
+
+_by_sqlstate['22P06'] = NonstandardUseOfEscapeCharacter
+
+
+# Class 23 - Integrity Constraint Violation
+
+
+class IntegrityConstraintViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23000'] = IntegrityConstraintViolation
+
+
+class RestrictViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23001'] = RestrictViolation
+
+
+class NotNullViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23502'] = NotNullViolation
+
+
+class ForeignKeyViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23503'] = ForeignKeyViolation
+
+
+class UniqueViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23505'] = UniqueViolation
+
+
+class CheckViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23514'] = CheckViolation
+
+
+class ExclusionViolation(IntegrityError):
+ pass
+
+_by_sqlstate['23P01'] = ExclusionViolation
+
+
+# Class 24 - Invalid Cursor State
+
+
+class InvalidCursorState(InternalError):
+ pass
+
+_by_sqlstate['24000'] = InvalidCursorState
+
+
+# Class 25 - Invalid Transaction State
+
+
+class InvalidTransactionState(InternalError):
+ pass
+
+_by_sqlstate['25000'] = InvalidTransactionState
+
+
+class ActiveSqlTransaction(InternalError):
+ pass
+
+_by_sqlstate['25001'] = ActiveSqlTransaction
+
+
+class BranchTransactionAlreadyActive(InternalError):
+ pass
+
+_by_sqlstate['25002'] = BranchTransactionAlreadyActive
+
+
+class InappropriateAccessModeForBranchTransaction(InternalError):
+ pass
+
+_by_sqlstate['25003'] = InappropriateAccessModeForBranchTransaction
+
+
+class InappropriateIsolationLevelForBranchTransaction(InternalError):
+ pass
+
+_by_sqlstate['25004'] = InappropriateIsolationLevelForBranchTransaction
+
+
+class NoActiveSqlTransactionForBranchTransaction(InternalError):
+ pass
+
+_by_sqlstate['25005'] = NoActiveSqlTransactionForBranchTransaction
+
+
+class ReadOnlySqlTransaction(InternalError):
+ pass
+
+_by_sqlstate['25006'] = ReadOnlySqlTransaction
+
+
+class SchemaAndDataStatementMixingNotSupported(InternalError):
+ pass
+
+_by_sqlstate['25007'] = SchemaAndDataStatementMixingNotSupported
+
+
+class HeldCursorRequiresSameIsolationLevel(InternalError):
+ pass
+
+_by_sqlstate['25008'] = HeldCursorRequiresSameIsolationLevel
+
+
+class NoActiveSqlTransaction(InternalError):
+ pass
+
+_by_sqlstate['25P01'] = NoActiveSqlTransaction
+
+
+class InFailedSqlTransaction(InternalError):
+ pass
+
+_by_sqlstate['25P02'] = InFailedSqlTransaction
+
+
+class IdleInTransactionSessionTimeout(InternalError):
+ pass
+
+_by_sqlstate['25P03'] = IdleInTransactionSessionTimeout
+
+
+# Class 26 - Invalid SQL Statement Name
+
+
+class InvalidSqlStatementName(OperationalError):
+ pass
+
+_by_sqlstate['26000'] = InvalidSqlStatementName
+
+
+# Class 27 - Triggered Data Change Violation
+
+
+class TriggeredDataChangeViolation(OperationalError):
+ pass
+
+_by_sqlstate['27000'] = TriggeredDataChangeViolation
+
+
+# Class 28 - Invalid Authorization Specification
+
+
+class InvalidAuthorizationSpecification(OperationalError):
+ pass
+
+_by_sqlstate['28000'] = InvalidAuthorizationSpecification
+
+
+class InvalidPassword(OperationalError):
+ pass
+
+_by_sqlstate['28P01'] = InvalidPassword
+
+
+# Class 2B - Dependent Privilege Descriptors Still Exist
+
+
+class DependentPrivilegeDescriptorsStillExist(InternalError):
+ pass
+
+_by_sqlstate['2B000'] = DependentPrivilegeDescriptorsStillExist
+
+
+class DependentObjectsStillExist(InternalError):
+ pass
+
+_by_sqlstate['2BP01'] = DependentObjectsStillExist
+
+
+# Class 2D - Invalid Transaction Termination
+
+
+class InvalidTransactionTermination(InternalError):
+ pass
+
+_by_sqlstate['2D000'] = InvalidTransactionTermination
+
+
+# Class 2F - SQL Routine Exception
+
+
+class SqlRoutineException(InternalError):
+ pass
+
+_by_sqlstate['2F000'] = SqlRoutineException
+
+
+class ModifyingSqlDataNotPermitted(InternalError):
+ pass
+
+_by_sqlstate['2F002'] = ModifyingSqlDataNotPermitted
+
+
+class ProhibitedSqlStatementAttempted(InternalError):
+ pass
+
+_by_sqlstate['2F003'] = ProhibitedSqlStatementAttempted
+
+
+class ReadingSqlDataNotPermitted(InternalError):
+ pass
+
+_by_sqlstate['2F004'] = ReadingSqlDataNotPermitted
+
+
+class FunctionExecutedNoReturnStatement(InternalError):
+ pass
+
+_by_sqlstate['2F005'] = FunctionExecutedNoReturnStatement
+
+
+# Class 34 - Invalid Cursor Name
+
+
+class InvalidCursorName(OperationalError):
+ pass
+
+_by_sqlstate['34000'] = InvalidCursorName
+
+
+# Class 38 - External Routine Exception
+
+
+class ExternalRoutineException(InternalError):
+ pass
+
+_by_sqlstate['38000'] = ExternalRoutineException
+
+
+class ContainingSqlNotPermitted(InternalError):
+ pass
+
+_by_sqlstate['38001'] = ContainingSqlNotPermitted
+
+
+class ModifyingSqlDataNotPermitted(InternalError):
+ pass
+
+_by_sqlstate['38002'] = ModifyingSqlDataNotPermitted
+
+
+class ProhibitedSqlStatementAttempted(InternalError):
+ pass
+
+_by_sqlstate['38003'] = ProhibitedSqlStatementAttempted
+
+
+class ReadingSqlDataNotPermitted(InternalError):
+ pass
+
+_by_sqlstate['38004'] = ReadingSqlDataNotPermitted
+
+
+# Class 39 - External Routine Invocation Exception
+
+
+class ExternalRoutineInvocationException(InternalError):
+ pass
+
+_by_sqlstate['39000'] = ExternalRoutineInvocationException
+
+
+class InvalidSqlstateReturned(InternalError):
+ pass
+
+_by_sqlstate['39001'] = InvalidSqlstateReturned
+
+
+class NullValueNotAllowed(InternalError):
+ pass
+
+_by_sqlstate['39004'] = NullValueNotAllowed
+
+
+class TriggerProtocolViolated(InternalError):
+ pass
+
+_by_sqlstate['39P01'] = TriggerProtocolViolated
+
+
+class SrfProtocolViolated(InternalError):
+ pass
+
+_by_sqlstate['39P02'] = SrfProtocolViolated
+
+
+class EventTriggerProtocolViolated(InternalError):
+ pass
+
+_by_sqlstate['39P03'] = EventTriggerProtocolViolated
+
+
+# Class 3B - Savepoint Exception
+
+
+class SavepointException(InternalError):
+ pass
+
+_by_sqlstate['3B000'] = SavepointException
+
+
+class InvalidSavepointSpecification(InternalError):
+ pass
+
+_by_sqlstate['3B001'] = InvalidSavepointSpecification
+
+
+# Class 3D - Invalid Catalog Name
+
+
+class InvalidCatalogName(ProgrammingError):
+ pass
+
+_by_sqlstate['3D000'] = InvalidCatalogName
+
+
+# Class 3F - Invalid Schema Name
+
+
+class InvalidSchemaName(ProgrammingError):
+ pass
+
+_by_sqlstate['3F000'] = InvalidSchemaName
+
+
+# Class 40 - Transaction Rollback
+
+
+class TransactionRollback(TransactionRollbackError):
+ pass
+
+_by_sqlstate['40000'] = TransactionRollback
+
+
+class SerializationFailure(TransactionRollbackError):
+ pass
+
+_by_sqlstate['40001'] = SerializationFailure
+
+
+class TransactionIntegrityConstraintViolation(TransactionRollbackError):
+ pass
+
+_by_sqlstate['40002'] = TransactionIntegrityConstraintViolation
+
+
+class StatementCompletionUnknown(TransactionRollbackError):
+ pass
+
+_by_sqlstate['40003'] = StatementCompletionUnknown
+
+
+class DeadlockDetected(TransactionRollbackError):
+ pass
+
+_by_sqlstate['40P01'] = DeadlockDetected
+
+
+# Class 42 - Syntax Error or Access Rule Violation
+
+
+class SyntaxErrorOrAccessRuleViolation(ProgrammingError):
+ pass
+
+_by_sqlstate['42000'] = SyntaxErrorOrAccessRuleViolation
+
+
+class InsufficientPrivilege(ProgrammingError):
+ pass
+
+_by_sqlstate['42501'] = InsufficientPrivilege
+
+
+class SyntaxError(ProgrammingError):
+ pass
+
+_by_sqlstate['42601'] = SyntaxError
+
+
+class InvalidName(ProgrammingError):
+ pass
+
+_by_sqlstate['42602'] = InvalidName
+
+
+class InvalidColumnDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42611'] = InvalidColumnDefinition
+
+
+class NameTooLong(ProgrammingError):
+ pass
+
+_by_sqlstate['42622'] = NameTooLong
+
+
+class DuplicateColumn(ProgrammingError):
+ pass
+
+_by_sqlstate['42701'] = DuplicateColumn
+
+
+class AmbiguousColumn(ProgrammingError):
+ pass
+
+_by_sqlstate['42702'] = AmbiguousColumn
+
+
+class UndefinedColumn(ProgrammingError):
+ pass
+
+_by_sqlstate['42703'] = UndefinedColumn
+
+
+class UndefinedObject(ProgrammingError):
+ pass
+
+_by_sqlstate['42704'] = UndefinedObject
+
+
+class DuplicateObject(ProgrammingError):
+ pass
+
+_by_sqlstate['42710'] = DuplicateObject
+
+
+class DuplicateAlias(ProgrammingError):
+ pass
+
+_by_sqlstate['42712'] = DuplicateAlias
+
+
+class DuplicateFunction(ProgrammingError):
+ pass
+
+_by_sqlstate['42723'] = DuplicateFunction
+
+
+class AmbiguousFunction(ProgrammingError):
+ pass
+
+_by_sqlstate['42725'] = AmbiguousFunction
+
+
+class GroupingError(ProgrammingError):
+ pass
+
+_by_sqlstate['42803'] = GroupingError
+
+
+class DatatypeMismatch(ProgrammingError):
+ pass
+
+_by_sqlstate['42804'] = DatatypeMismatch
+
+
+class WrongObjectType(ProgrammingError):
+ pass
+
+_by_sqlstate['42809'] = WrongObjectType
+
+
+class InvalidForeignKey(ProgrammingError):
+ pass
+
+_by_sqlstate['42830'] = InvalidForeignKey
+
+
+class CannotCoerce(ProgrammingError):
+ pass
+
+_by_sqlstate['42846'] = CannotCoerce
+
+
+class UndefinedFunction(ProgrammingError):
+ pass
+
+_by_sqlstate['42883'] = UndefinedFunction
+
+
+class GeneratedAlways(ProgrammingError):
+ pass
+
+_by_sqlstate['428C9'] = GeneratedAlways
+
+
+class ReservedName(ProgrammingError):
+ pass
+
+_by_sqlstate['42939'] = ReservedName
+
+
+class UndefinedTable(ProgrammingError):
+ pass
+
+_by_sqlstate['42P01'] = UndefinedTable
+
+
+class UndefinedParameter(ProgrammingError):
+ pass
+
+_by_sqlstate['42P02'] = UndefinedParameter
+
+
+class DuplicateCursor(ProgrammingError):
+ pass
+
+_by_sqlstate['42P03'] = DuplicateCursor
+
+
+class DuplicateDatabase(ProgrammingError):
+ pass
+
+_by_sqlstate['42P04'] = DuplicateDatabase
+
+
+class DuplicatePreparedStatement(ProgrammingError):
+ pass
+
+_by_sqlstate['42P05'] = DuplicatePreparedStatement
+
+
+class DuplicateSchema(ProgrammingError):
+ pass
+
+_by_sqlstate['42P06'] = DuplicateSchema
+
+
+class DuplicateTable(ProgrammingError):
+ pass
+
+_by_sqlstate['42P07'] = DuplicateTable
+
+
+class AmbiguousParameter(ProgrammingError):
+ pass
+
+_by_sqlstate['42P08'] = AmbiguousParameter
+
+
+class AmbiguousAlias(ProgrammingError):
+ pass
+
+_by_sqlstate['42P09'] = AmbiguousAlias
+
+
+class InvalidColumnReference(ProgrammingError):
+ pass
+
+_by_sqlstate['42P10'] = InvalidColumnReference
+
+
+class InvalidCursorDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P11'] = InvalidCursorDefinition
+
+
+class InvalidDatabaseDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P12'] = InvalidDatabaseDefinition
+
+
+class InvalidFunctionDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P13'] = InvalidFunctionDefinition
+
+
+class InvalidPreparedStatementDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P14'] = InvalidPreparedStatementDefinition
+
+
+class InvalidSchemaDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P15'] = InvalidSchemaDefinition
+
+
+class InvalidTableDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P16'] = InvalidTableDefinition
+
+
+class InvalidObjectDefinition(ProgrammingError):
+ pass
+
+_by_sqlstate['42P17'] = InvalidObjectDefinition
+
+
+class IndeterminateDatatype(ProgrammingError):
+ pass
+
+_by_sqlstate['42P18'] = IndeterminateDatatype
+
+
+class InvalidRecursion(ProgrammingError):
+ pass
+
+_by_sqlstate['42P19'] = InvalidRecursion
+
+
+class WindowingError(ProgrammingError):
+ pass
+
+_by_sqlstate['42P20'] = WindowingError
+
+
+class CollationMismatch(ProgrammingError):
+ pass
+
+_by_sqlstate['42P21'] = CollationMismatch
+
+
+class IndeterminateCollation(ProgrammingError):
+ pass
+
+_by_sqlstate['42P22'] = IndeterminateCollation
+
+
+# Class 44 - WITH CHECK OPTION Violation
+
+
+class WithCheckOptionViolation(ProgrammingError):
+ pass
+
+_by_sqlstate['44000'] = WithCheckOptionViolation
+
+
+# Class 53 - Insufficient Resources
+
+
+class InsufficientResources(OperationalError):
+ pass
+
+_by_sqlstate['53000'] = InsufficientResources
+
+
+class DiskFull(OperationalError):
+ pass
+
+_by_sqlstate['53100'] = DiskFull
+
+
+class OutOfMemory(OperationalError):
+ pass
+
+_by_sqlstate['53200'] = OutOfMemory
+
+
+class TooManyConnections(OperationalError):
+ pass
+
+_by_sqlstate['53300'] = TooManyConnections
+
+
+class ConfigurationLimitExceeded(OperationalError):
+ pass
+
+_by_sqlstate['53400'] = ConfigurationLimitExceeded
+
+
+# Class 54 - Program Limit Exceeded
+
+
+class ProgramLimitExceeded(OperationalError):
+ pass
+
+_by_sqlstate['54000'] = ProgramLimitExceeded
+
+
+class StatementTooComplex(OperationalError):
+ pass
+
+_by_sqlstate['54001'] = StatementTooComplex
+
+
+class TooManyColumns(OperationalError):
+ pass
+
+_by_sqlstate['54011'] = TooManyColumns
+
+
+class TooManyArguments(OperationalError):
+ pass
+
+_by_sqlstate['54023'] = TooManyArguments
+
+
+# Class 55 - Object Not In Prerequisite State
+
+
+class ObjectNotInPrerequisiteState(OperationalError):
+ pass
+
+_by_sqlstate['55000'] = ObjectNotInPrerequisiteState
+
+
+class ObjectInUse(OperationalError):
+ pass
+
+_by_sqlstate['55006'] = ObjectInUse
+
+
+class CantChangeRuntimeParam(OperationalError):
+ pass
+
+_by_sqlstate['55P02'] = CantChangeRuntimeParam
+
+
+class LockNotAvailable(OperationalError):
+ pass
+
+_by_sqlstate['55P03'] = LockNotAvailable
+
+
+# Class 57 - Operator Intervention
+
+
+class OperatorIntervention(OperationalError):
+ pass
+
+_by_sqlstate['57000'] = OperatorIntervention
+
+
+class QueryCanceled(QueryCanceledError):
+ pass
+
+_by_sqlstate['57014'] = QueryCanceled
+
+
+class AdminShutdown(OperationalError):
+ pass
+
+_by_sqlstate['57P01'] = AdminShutdown
+
+
+class CrashShutdown(OperationalError):
+ pass
+
+_by_sqlstate['57P02'] = CrashShutdown
+
+
+class CannotConnectNow(OperationalError):
+ pass
+
+_by_sqlstate['57P03'] = CannotConnectNow
+
+
+class DatabaseDropped(OperationalError):
+ pass
+
+_by_sqlstate['57P04'] = DatabaseDropped
+
+
+# Class 58 - System Error (errors external to PostgreSQL itself)
+
+
+class SystemError(OperationalError):
+ pass
+
+_by_sqlstate['58000'] = SystemError
+
+
+class IoError(OperationalError):
+ pass
+
+_by_sqlstate['58030'] = IoError
+
+
+class UndefinedFile(OperationalError):
+ pass
+
+_by_sqlstate['58P01'] = UndefinedFile
+
+
+class DuplicateFile(OperationalError):
+ pass
+
+_by_sqlstate['58P02'] = DuplicateFile
+
+
+# Class 72 - Snapshot Failure
+
+
+class SnapshotTooOld(DatabaseError):
+ pass
+
+_by_sqlstate['72000'] = SnapshotTooOld
+
+
+# Class F0 - Configuration File Error
+
+
+class ConfigFileError(InternalError):
+ pass
+
+_by_sqlstate['F0000'] = ConfigFileError
+
+
+class LockFileExists(InternalError):
+ pass
+
+_by_sqlstate['F0001'] = LockFileExists
+
+
+# Class HV - Foreign Data Wrapper Error (SQL/MED)
+
+
+class FdwError(OperationalError):
+ pass
+
+_by_sqlstate['HV000'] = FdwError
+
+
+class FdwOutOfMemory(OperationalError):
+ pass
+
+_by_sqlstate['HV001'] = FdwOutOfMemory
+
+
+class FdwDynamicParameterValueNeeded(OperationalError):
+ pass
+
+_by_sqlstate['HV002'] = FdwDynamicParameterValueNeeded
+
+
+class FdwInvalidDataType(OperationalError):
+ pass
+
+_by_sqlstate['HV004'] = FdwInvalidDataType
+
+
+class FdwColumnNameNotFound(OperationalError):
+ pass
+
+_by_sqlstate['HV005'] = FdwColumnNameNotFound
+
+
+class FdwInvalidDataTypeDescriptors(OperationalError):
+ pass
+
+_by_sqlstate['HV006'] = FdwInvalidDataTypeDescriptors
+
+
+class FdwInvalidColumnName(OperationalError):
+ pass
+
+_by_sqlstate['HV007'] = FdwInvalidColumnName
+
+
+class FdwInvalidColumnNumber(OperationalError):
+ pass
+
+_by_sqlstate['HV008'] = FdwInvalidColumnNumber
+
+
+class FdwInvalidUseOfNullPointer(OperationalError):
+ pass
+
+_by_sqlstate['HV009'] = FdwInvalidUseOfNullPointer
+
+
+class FdwInvalidStringFormat(OperationalError):
+ pass
+
+_by_sqlstate['HV00A'] = FdwInvalidStringFormat
+
+
+class FdwInvalidHandle(OperationalError):
+ pass
+
+_by_sqlstate['HV00B'] = FdwInvalidHandle
+
+
+class FdwInvalidOptionIndex(OperationalError):
+ pass
+
+_by_sqlstate['HV00C'] = FdwInvalidOptionIndex
+
+
+class FdwInvalidOptionName(OperationalError):
+ pass
+
+_by_sqlstate['HV00D'] = FdwInvalidOptionName
+
+
+class FdwOptionNameNotFound(OperationalError):
+ pass
+
+_by_sqlstate['HV00J'] = FdwOptionNameNotFound
+
+
+class FdwReplyHandle(OperationalError):
+ pass
+
+_by_sqlstate['HV00K'] = FdwReplyHandle
+
+
+class FdwUnableToCreateExecution(OperationalError):
+ pass
+
+_by_sqlstate['HV00L'] = FdwUnableToCreateExecution
+
+
+class FdwUnableToCreateReply(OperationalError):
+ pass
+
+_by_sqlstate['HV00M'] = FdwUnableToCreateReply
+
+
+class FdwUnableToEstablishConnection(OperationalError):
+ pass
+
+_by_sqlstate['HV00N'] = FdwUnableToEstablishConnection
+
+
+class FdwNoSchemas(OperationalError):
+ pass
+
+_by_sqlstate['HV00P'] = FdwNoSchemas
+
+
+class FdwSchemaNotFound(OperationalError):
+ pass
+
+_by_sqlstate['HV00Q'] = FdwSchemaNotFound
+
+
+class FdwTableNotFound(OperationalError):
+ pass
+
+_by_sqlstate['HV00R'] = FdwTableNotFound
+
+
+class FdwFunctionSequenceError(OperationalError):
+ pass
+
+_by_sqlstate['HV010'] = FdwFunctionSequenceError
+
+
+class FdwTooManyHandles(OperationalError):
+ pass
+
+_by_sqlstate['HV014'] = FdwTooManyHandles
+
+
+class FdwInconsistentDescriptorInformation(OperationalError):
+ pass
+
+_by_sqlstate['HV021'] = FdwInconsistentDescriptorInformation
+
+
+class FdwInvalidAttributeValue(OperationalError):
+ pass
+
+_by_sqlstate['HV024'] = FdwInvalidAttributeValue
+
+
+class FdwInvalidStringLengthOrBufferLength(OperationalError):
+ pass
+
+_by_sqlstate['HV090'] = FdwInvalidStringLengthOrBufferLength
+
+
+class FdwInvalidDescriptorFieldIdentifier(OperationalError):
+ pass
+
+_by_sqlstate['HV091'] = FdwInvalidDescriptorFieldIdentifier
+
+
+# Class P0 - PL/pgSQL Error
+
+
+class PlpgsqlError(InternalError):
+ pass
+
+_by_sqlstate['P0000'] = PlpgsqlError
+
+
+class RaiseException(InternalError):
+ pass
+
+_by_sqlstate['P0001'] = RaiseException
+
+
+class NoDataFound(InternalError):
+ pass
+
+_by_sqlstate['P0002'] = NoDataFound
+
+
+class TooManyRows(InternalError):
+ pass
+
+_by_sqlstate['P0003'] = TooManyRows
+
+
+class AssertFailure(InternalError):
+ pass
+
+_by_sqlstate['P0004'] = AssertFailure
+
+
+# Class XX - Internal Error
+
+
+class InternalError(InternalError):
+ pass
+
+_by_sqlstate['XX000'] = InternalError
+
+
+class DataCorrupted(InternalError):
+ pass
+
+_by_sqlstate['XX001'] = DataCorrupted
+
+
+class IndexCorrupted(InternalError):
+ pass
+
+_by_sqlstate['XX002'] = IndexCorrupted
diff --git a/psycopg/pqpath.c b/psycopg/pqpath.c
index 2893739..04a34a2 100644
--- a/psycopg/pqpath.c
+++ b/psycopg/pqpath.c
@@ -77,6 +77,35 @@ strip_severity(const char *msg)
return msg;
}
+/* Return a Python exception from a SQLSTATE from psycopg2.errors */
+BORROWED static PyObject *
+exception_from_module(const char *sqlstate)
+{
+ PyObject *rv = NULL;
+ PyObject *m = NULL;
+ PyObject *map = NULL;
+
+ if (!(m = PyImport_ImportModule("psycopg2.errors"))) { goto exit; }
+ if (!(map = PyObject_GetAttrString(m, "_by_sqlstate"))) { goto exit; }
+ if (!PyDict_Check(map)) {
+ Dprintf("'psycopg2.errors._by_sqlstate' is not a dict!");
+ goto exit;
+ }
+
+ /* get the sqlstate class (borrowed reference), or fail trying. */
+ rv = PyDict_GetItemString(map, sqlstate);
+
+exit:
+ /* We exit with a borrowed object, or a NULL but no error
+ * If an error did happen in this function, we don't want to clobber the
+ * database error. So better reporting it, albeit with the wrong class. */
+ PyErr_Clear();
+
+ Py_XDECREF(map);
+ Py_XDECREF(m);
+ return rv;
+}
+
/* Returns the Python exception corresponding to an SQLSTATE error
code. A list of error codes can be found at:
@@ -84,6 +113,21 @@ strip_severity(const char *msg)
BORROWED static PyObject *
exception_from_sqlstate(const char *sqlstate)
{
+ PyObject *exc;
+
+ /* First look up an exception of the proper class from the Python module */
+ exc = exception_from_module(sqlstate);
+ if (exc) {
+ return exc;
+ }
+ else {
+ PyErr_Clear();
+ }
+
+ /*
+ * IMPORTANT: if you change anything in this function you should change
+ * make_errors.py accordingly.
+ */
switch (sqlstate[0]) {
case '0':
switch (sqlstate[1]) {
diff --git a/scripts/make_errorcodes.py b/scripts/make_errorcodes.py
index 1b3f594..3c72a2c 100755
--- a/scripts/make_errorcodes.py
+++ b/scripts/make_errorcodes.py
@@ -22,8 +22,6 @@ import sys
import urllib2
from collections import defaultdict
-from BeautifulSoup import BeautifulSoup as BS
-
def main():
if len(sys.argv) != 2:
@@ -35,8 +33,7 @@ def main():
file_start = read_base_file(filename)
# If you add a version to the list fix the docs (in errorcodes.rst)
classes, errors = fetch_errors(
- ['8.1', '8.2', '8.3', '8.4', '9.0', '9.1', '9.2', '9.3', '9.4', '9.5',
- '9.6', '10', '11'])
+ ['9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '10', '11'])
f = open(filename, "w")
for line in file_start:
@@ -90,48 +87,6 @@ def parse_errors_txt(url):
return classes, errors
-def parse_errors_sgml(url):
- page = BS(urllib2.urlopen(url))
- table = page('table')[1]('tbody')[0]
-
- classes = {}
- errors = defaultdict(dict)
-
- for tr in table('tr'):
- if tr.td.get('colspan'): # it's a class
- label = ' '.join(' '.join(tr(text=True)).split()) \
- .replace(u'\u2014', '-').encode('ascii')
- assert label.startswith('Class')
- class_ = label.split()[1]
- assert len(class_) == 2
- classes[class_] = label
-
- else: # it's an error
- errcode = tr.tt.string.encode("ascii")
- assert len(errcode) == 5
-
- tds = tr('td')
- if len(tds) == 3:
- errlabel = '_'.join(tds[1].string.split()).encode('ascii')
-
- # double check the columns are equal
- cond_name = tds[2].string.strip().upper().encode("ascii")
- assert errlabel == cond_name, tr
-
- elif len(tds) == 2:
- # found in PG 9.1 docs
- errlabel = tds[1].tt.string.upper().encode("ascii")
-
- else:
- assert False, tr
-
- errors[class_][errcode] = errlabel
-
- return classes, errors
-
-errors_sgml_url = \
- "https://www.postgresql.org/docs/%s/static/errcodes-appendix.html"
-
errors_txt_url = \
"http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob_plain;" \
"f=src/backend/utils/errcodes.txt;hb=%s"
@@ -144,15 +99,16 @@ def fetch_errors(versions):
for version in versions:
print(version, file=sys.stderr)
tver = tuple(map(int, version.split()[0].split('.')))
- if tver < (9, 1):
- c1, e1 = parse_errors_sgml(errors_sgml_url % version)
- else:
- tag = '%s%s_STABLE' % (
- (tver[0] >= 10 and 'REL_' or 'REL'),
- version.replace('.', '_'))
- c1, e1 = parse_errors_txt(errors_txt_url % tag)
+ tag = '%s%s_STABLE' % (
+ (tver[0] >= 10 and 'REL_' or 'REL'),
+ version.replace('.', '_'))
+ c1, e1 = parse_errors_txt(errors_txt_url % tag)
classes.update(c1)
+ # This error was in old server versions but probably never used
+ # https://github.com/postgres/postgres/commit/12f87b2c82
+ errors['22']['22020'] = 'INVALID_LIMIT_VALUE'
+
# TODO: this error was added in PG 10 beta 1 but dropped in the
# final release. It doesn't harm leaving it in the file. Check if it
# will be added back in PG 12.
diff --git a/scripts/make_errors.py b/scripts/make_errors.py
new file mode 100755
index 0000000..cdf299f
--- /dev/null
+++ b/scripts/make_errors.py
@@ -0,0 +1,210 @@
+#!/usr/bin/env python
+"""Generate the errors module from PostgreSQL source code.
+
+The script can be run at a new PostgreSQL release to refresh the module.
+"""
+
+# Copyright (C) 2018 Daniele Varrazzo <daniele.varrazzo@gmail.com>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+from __future__ import print_function
+
+import re
+import sys
+import urllib2
+from collections import defaultdict
+
+
+def main():
+ if len(sys.argv) != 2:
+ print("usage: %s /path/to/errors.py" % sys.argv[0], file=sys.stderr)
+ return 2
+
+ filename = sys.argv[1]
+
+ file_start = read_base_file(filename)
+ # If you add a version to the list fix the docs (in errors.rst)
+ classes, errors = fetch_errors(
+ ['9.1', '9.2', '9.3', '9.4', '9.5', '9.6', '10', '11'])
+
+ f = open(filename, "w")
+ for line in file_start:
+ print(line, file=f)
+ for line in generate_module_data(classes, errors):
+ print(line, file=f)
+
+
+def read_base_file(filename):
+ rv = []
+ for line in open(filename):
+ rv.append(line.rstrip("\n"))
+ if line.startswith("# autogenerated"):
+ return rv
+
+ raise ValueError("can't find the separator. Is this the right file?")
+
+
+def parse_errors_txt(url):
+ classes = {}
+ errors = defaultdict(dict)
+
+ page = urllib2.urlopen(url)
+ for line in page:
+ # Strip comments and skip blanks
+ line = line.split('#')[0].strip()
+ if not line:
+ continue
+
+ # Parse a section
+ m = re.match(r"Section: (Class (..) - .+)", line)
+ if m:
+ label, class_ = m.groups()
+ classes[class_] = label
+ continue
+
+ # Parse an error
+ m = re.match(r"(.....)\s+(?:E|W|S)\s+ERRCODE_(\S+)(?:\s+(\S+))?$", line)
+ if m:
+ errcode, macro, spec = m.groups()
+ # skip errcodes without specs as they are not publically visible
+ if not spec:
+ continue
+ errlabel = spec.upper()
+ errors[class_][errcode] = errlabel
+ continue
+
+ # We don't expect anything else
+ raise ValueError("unexpected line:\n%s" % line)
+
+ return classes, errors
+
+
+errors_txt_url = \
+ "http://git.postgresql.org/gitweb/?p=postgresql.git;a=blob_plain;" \
+ "f=src/backend/utils/errcodes.txt;hb=%s"
+
+
+def fetch_errors(versions):
+ classes = {}
+ errors = defaultdict(dict)
+
+ for version in versions:
+ print(version, file=sys.stderr)
+ tver = tuple(map(int, version.split()[0].split('.')))
+ tag = '%s%s_STABLE' % (
+ (tver[0] >= 10 and 'REL_' or 'REL'),
+ version.replace('.', '_'))
+ c1, e1 = parse_errors_txt(errors_txt_url % tag)
+ classes.update(c1)
+
+ for c, cerrs in e1.items():
+ errors[c].update(cerrs)
+
+ return classes, errors
+
+
+def generate_module_data(classes, errors):
+ tmpl = """
+
+class %(cls)s(%(base)s):
+ pass
+
+_by_sqlstate[%(errcode)r] = %(cls)s\
+"""
+ for clscode, clslabel in sorted(classes.items()):
+ if clscode in ('00', '01'):
+ # success and warning - never raised
+ continue
+
+ yield "\n\n# %s" % clslabel
+
+ for errcode, errlabel in sorted(errors[clscode].items()):
+ clsname = errlabel.title().replace('_', '')
+ yield tmpl % {
+ 'cls': clsname,
+ 'base': get_base_class_name(errcode),
+ 'errcode': errcode
+ }
+
+
+def get_base_class_name(errcode):
+ """
+ This is a python porting of exception_from_sqlstate code in pqpath.c
+ """
+ if errcode[0] == '0':
+ if errcode[1] == 'A': # Class 0A - Feature Not Supported
+ return 'NotSupportedError'
+ elif errcode[0] == '2':
+ if errcode[1] in '01':
+ # Class 20 - Case Not Found
+ # Class 21 - Cardinality Violation
+ return 'ProgrammingError'
+ elif errcode[1] == '2': # Class 22 - Data Exception
+ return 'DataError'
+ elif errcode[1] == '3': # Class 23 - Integrity Constraint Violation
+ return 'IntegrityError'
+ elif errcode[1] in '45':
+ # Class 24 - Invalid Cursor State
+ # Class 25 - Invalid Transaction State
+ return 'InternalError'
+ elif errcode[1] in '678':
+ # Class 26 - Invalid SQL Statement Name
+ # Class 27 - Triggered Data Change Violation
+ # Class 28 - Invalid Authorization Specification
+ return 'OperationalError'
+ elif errcode[1] in 'BDF':
+ # Class 2B - Dependent Privilege Descriptors Still Exist
+ # Class 2D - Invalid Transaction Termination
+ # Class 2F - SQL Routine Exception
+ return 'InternalError'
+ elif errcode[0] == '3':
+ if errcode[1] == '4': # Class 34 - Invalid Cursor Name
+ return 'OperationalError'
+ if errcode[1] in '89B':
+ # Class 38 - External Routine Exception
+ # Class 39 - External Routine Invocation Exception
+ # Class 3B - Savepoint Exception
+ return 'InternalError'
+ if errcode[1] in 'DF':
+ # Class 3D - Invalid Catalog Name
+ # Class 3F - Invalid Schema Name
+ return 'ProgrammingError'
+ elif errcode[0] == '4':
+ if errcode[1] == '0': # Class 40 - Transaction Rollback
+ return 'TransactionRollbackError'
+ if errcode[1] in '24':
+ # Class 42 - Syntax Error or Access Rule Violation
+ # Class 44 - WITH CHECK OPTION Violation
+ return 'ProgrammingError'
+ elif errcode[0] == '5':
+ if errcode == "57014":
+ return 'QueryCanceledError'
+ # Class 53 - Insufficient Resources
+ # Class 54 - Program Limit Exceeded
+ # Class 55 - Object Not In Prerequisite State
+ # Class 57 - Operator Intervention
+ # Class 58 - System Error (errors external to PostgreSQL itself)
+ else:
+ return 'OperationalError'
+ elif errcode[0] == 'F': # Class F0 - Configuration File Error
+ return 'InternalError'
+ elif errcode[0] == 'H': # Class HV - Foreign Data Wrapper Error (SQL/MED)
+ return 'OperationalError'
+ elif errcode[0] == 'P': # Class P0 - PL/pgSQL Error
+ return 'InternalError'
+ elif errcode[0] == 'X': # Class XX - Internal Error
+ return 'InternalError'
+
+ return 'DatabaseError'
+
+
+if __name__ == '__main__':
+ sys.exit(main())
diff --git a/tests/__init__.py b/tests/__init__.py
index e58b6fa..5c57849 100755
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -40,6 +40,7 @@ from . import test_copy
from . import test_cursor
from . import test_dates
from . import test_errcodes
+from . import test_errors
from . import test_extras_dictcursor
from . import test_fast_executemany
from . import test_green
@@ -84,6 +85,7 @@ def test_suite():
suite.addTest(test_cursor.test_suite())
suite.addTest(test_dates.test_suite())
suite.addTest(test_errcodes.test_suite())
+ suite.addTest(test_errors.test_suite())
suite.addTest(test_extras_dictcursor.test_suite())
suite.addTest(test_fast_executemany.test_suite())
suite.addTest(test_green.test_suite())
diff --git a/tests/test_errors.py b/tests/test_errors.py
new file mode 100755
index 0000000..cb680a2
--- /dev/null
+++ b/tests/test_errors.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python
+
+# test_errors.py - unit test for psycopg2.errors module
+#
+# Copyright (C) 2018 Daniele Varrazzo <daniele.varrazzo@gmail.com>
+#
+# psycopg2 is free software: you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published
+# by the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# In addition, as a special exception, the copyright holders give
+# permission to link this program with the OpenSSL library (or with
+# modified versions of OpenSSL that use the same license as OpenSSL),
+# and distribute linked combinations including the two.
+#
+# You must obey the GNU Lesser General Public License in all respects for
+# all of the code used other than OpenSSL.
+#
+# psycopg2 is distributed in the hope that it will be useful, but WITHOUT
+# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+# FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
+# License for more details.
+
+import unittest
+from .testutils import ConnectingTestCase
+
+import psycopg2
+
+
+class ErrorsTests(ConnectingTestCase):
+ def test_exception_class(self):
+ cur = self.conn.cursor()
+ try:
+ cur.execute("select * from nonexist")
+ except psycopg2.Error as exc:
+ e = exc
+
+ from psycopg2.errors import UndefinedTable
+ self.assert_(isinstance(e, UndefinedTable), type(e))
+ self.assert_(isinstance(e, self.conn.ProgrammingError))
+
+ def test_exception_class_fallback(self):
+ cur = self.conn.cursor()
+
+ from psycopg2 import errors
+ x = errors._by_sqlstate.pop('42P01')
+ try:
+ cur.execute("select * from nonexist")
+ except psycopg2.Error as exc:
+ e = exc
+ finally:
+ errors._by_sqlstate['42P01'] = x
+
+ self.assertEqual(type(e), self.conn.ProgrammingError)
+
+ def test_lookup(self):
+ from psycopg2 import errors
+
+ self.assertIs(errors.lookup('42P01'), errors.UndefinedTable)
+
+ with self.assertRaises(KeyError):
+ errors.lookup('XXXXX')
+
+
+def test_suite():
+ return unittest.TestLoader().loadTestsFromName(__name__)
+
+if __name__ == "__main__":
+ unittest.main()