diff options
-rw-r--r-- | NEWS | 2 | ||||
-rw-r--r-- | doc/src/errors.rst | 60 | ||||
-rw-r--r-- | doc/src/extensions.rst | 7 | ||||
-rw-r--r-- | doc/src/index.rst | 1 | ||||
-rw-r--r-- | doc/src/module.rst | 17 | ||||
-rw-r--r-- | lib/errors.py | 1539 | ||||
-rw-r--r-- | psycopg/pqpath.c | 44 | ||||
-rwxr-xr-x | scripts/make_errorcodes.py | 62 | ||||
-rwxr-xr-x | scripts/make_errors.py | 210 | ||||
-rwxr-xr-x | tests/__init__.py | 2 | ||||
-rwxr-xr-x | tests/test_errors.py | 70 |
11 files changed, 1952 insertions, 62 deletions
@@ -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() |