]> git.ipfire.org Git - thirdparty/psycopg.git/commitdiff
Added mapping from sqlstate to error classes
authorDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 30 May 2020 21:14:50 +0000 (09:14 +1200)
committerDaniele Varrazzo <daniele.varrazzo@gmail.com>
Sat, 30 May 2020 21:18:25 +0000 (09:18 +1200)
psycopg3/errors.py
tests/test_errors.py
tools/update_errors.py [new file with mode: 0755]

index 3f4d8c0fe78f0666c2ec9d68e87076c10f31d915..c16f7390b45a92fd347320ea7e308c12cc50443b 100644 (file)
@@ -18,7 +18,7 @@ DBAPI-defined Exceptions are defined in the following hierarchy::
 
 # Copyright (C) 2020 The Psycopg Team
 
-from typing import Any, Optional, Sequence, Type
+from typing import Any, Callable, Dict, Optional, Sequence, Type
 from psycopg3.pq.proto import PGresult
 from psycopg3.pq.enums import DiagnosticField
 
@@ -200,14 +200,1451 @@ class Diagnostic:
         return None
 
 
-def class_for_state(sqlstate: bytes) -> Type[Error]:
-    # TODO: stub
-    return DatabaseError
+def lookup(sqlstate: str) -> Type[Error]:
+    return _sqlcodes[sqlstate]
 
 
 def error_from_result(result: PGresult, encoding: str = "utf-8") -> Error:
     from psycopg3 import pq
 
     state = result.error_field(DiagnosticField.SQLSTATE) or b""
-    cls = class_for_state(state)
+    cls = _class_for_state(state.decode("ascii"))
     return cls(pq.error_message(result), pgresult=result, encoding=encoding)
+
+
+def _class_for_state(sqlstate: str) -> Type[Error]:
+    try:
+        return lookup(sqlstate)
+    except KeyError:
+        return get_base_exception(sqlstate)
+
+
+def get_base_exception(sqlstate: str) -> Type[Error]:
+    exc = _base_exc_map.get(sqlstate[:2])
+    if exc is not None:
+        return exc
+
+    exc = _base_exc_map.get(sqlstate[0])
+    if exc is not None:
+        return exc
+
+    return DatabaseError
+
+
+_base_exc_map = {
+    "0A": NotSupportedError,  # Feature Not Supported
+    "20": ProgrammingError,  # Case Not Foud
+    "21": ProgrammingError,  # Cardinality Violation
+    "22": DataError,  # Data Exception
+    "23": IntegrityError,  # Integrity Constraint Violation
+    "24": InternalError,  # Invalid Cursor State
+    "25": InternalError,  # Invalid Transaction State
+    "26": ProgrammingError,  # Invalid SQL Statement Name *
+    "27": OperationalError,  # Triggered Data Change Violation
+    "28": OperationalError,  # Invalid Authorization Specification
+    "2B": InternalError,  # Dependent Privilege Descriptors Still Exist
+    "2D": InternalError,  # Invalid Transaction Termination
+    "2F": OperationalError,  # SQL Routine Exception *
+    "34": ProgrammingError,  # Invalid Cursor Name *
+    "38": OperationalError,  # External Routine Exception *
+    "39": OperationalError,  # External Routine Invocation Exception *
+    "3B": OperationalError,  # Savepoint Exception *
+    "3D": ProgrammingError,  # Invalid Catalog Name
+    "3F": ProgrammingError,  # Invalid Schema Name
+    "40": OperationalError,  # Transaction Rollback
+    "42": ProgrammingError,  # Syntax Error or Access Rule Violation
+    "44": ProgrammingError,  # WITH CHECK OPTION Violation
+    "53": OperationalError,  # Insufficient Resources
+    "54": OperationalError,  # Program Limit Exceeded
+    "55": OperationalError,  # Object Not In Prerequisite State
+    "57": OperationalError,  # Operator Intervention
+    "58": OperationalError,  # System Error (errors external to PostgreSQL itself)
+    "F": OperationalError,  # Configuration File Error
+    "H": OperationalError,  # Foreign Data Wrapper Error (SQL/MED)
+    "P": ProgrammingError,  # PL/pgSQL Error
+    "X": InternalError,  # Internal Error
+}
+
+
+def sqlcode(code: str) -> Callable[[Type[Error]], Type[Error]]:
+    """
+    Decorator to associate an exception class to a sqlstate.
+    """
+
+    def sqlcode_(cls: Type[Error]) -> Type[Error]:
+        _sqlcodes[code] = cls
+        return cls
+
+    return sqlcode_
+
+
+_sqlcodes: Dict[str, Type[Error]] = {}
+
+
+# Classes generated by toosls/update_errors.py
+# autogenerated: start
+
+# Class 02 - No Data (this is also a warning class per the SQL standard)
+
+
+@sqlcode("02000")
+class NoData(DatabaseError):
+    pass
+
+
+@sqlcode("02001")
+class NoAdditionalDynamicResultSetsReturned(DatabaseError):
+    pass
+
+
+# Class 03 - SQL Statement Not Yet Complete
+
+
+@sqlcode("03000")
+class SqlStatementNotYetComplete(DatabaseError):
+    pass
+
+
+# Class 08 - Connection Exception
+
+
+@sqlcode("08000")
+class ConnectionException(DatabaseError):
+    pass
+
+
+@sqlcode("08001")
+class SqlclientUnableToEstablishSqlconnection(DatabaseError):
+    pass
+
+
+@sqlcode("08003")
+class ConnectionDoesNotExist(DatabaseError):
+    pass
+
+
+@sqlcode("08004")
+class SqlserverRejectedEstablishmentOfSqlconnection(DatabaseError):
+    pass
+
+
+@sqlcode("08006")
+class ConnectionFailure(DatabaseError):
+    pass
+
+
+@sqlcode("08007")
+class TransactionResolutionUnknown(DatabaseError):
+    pass
+
+
+@sqlcode("08P01")
+class ProtocolViolation(DatabaseError):
+    pass
+
+
+# Class 09 - Triggered Action Exception
+
+
+@sqlcode("09000")
+class TriggeredActionException(DatabaseError):
+    pass
+
+
+# Class 0A - Feature Not Supported
+
+
+@sqlcode("0A000")
+class FeatureNotSupported(NotSupportedError):
+    pass
+
+
+# Class 0B - Invalid Transaction Initiation
+
+
+@sqlcode("0B000")
+class InvalidTransactionInitiation(DatabaseError):
+    pass
+
+
+# Class 0F - Locator Exception
+
+
+@sqlcode("0F000")
+class LocatorException(DatabaseError):
+    pass
+
+
+@sqlcode("0F001")
+class InvalidLocatorSpecification(DatabaseError):
+    pass
+
+
+# Class 0L - Invalid Grantor
+
+
+@sqlcode("0L000")
+class InvalidGrantor(DatabaseError):
+    pass
+
+
+@sqlcode("0LP01")
+class InvalidGrantOperation(DatabaseError):
+    pass
+
+
+# Class 0P - Invalid Role Specification
+
+
+@sqlcode("0P000")
+class InvalidRoleSpecification(DatabaseError):
+    pass
+
+
+# Class 0Z - Diagnostics Exception
+
+
+@sqlcode("0Z000")
+class DiagnosticsException(DatabaseError):
+    pass
+
+
+@sqlcode("0Z002")
+class StackedDiagnosticsAccessedWithoutActiveHandler(DatabaseError):
+    pass
+
+
+# Class 20 - Case Not Found
+
+
+@sqlcode("20000")
+class CaseNotFound(ProgrammingError):
+    pass
+
+
+# Class 21 - Cardinality Violation
+
+
+@sqlcode("21000")
+class CardinalityViolation(ProgrammingError):
+    pass
+
+
+# Class 22 - Data Exception
+
+
+@sqlcode("22000")
+class DataException(DataError):
+    pass
+
+
+@sqlcode("22001")
+class StringDataRightTruncation(DataError):
+    pass
+
+
+@sqlcode("22002")
+class NullValueNoIndicatorParameter(DataError):
+    pass
+
+
+@sqlcode("22003")
+class NumericValueOutOfRange(DataError):
+    pass
+
+
+@sqlcode("22004")
+class NullValueNotAllowed(DataError):
+    pass
+
+
+@sqlcode("22005")
+class ErrorInAssignment(DataError):
+    pass
+
+
+@sqlcode("22007")
+class InvalidDatetimeFormat(DataError):
+    pass
+
+
+@sqlcode("22008")
+class DatetimeFieldOverflow(DataError):
+    pass
+
+
+@sqlcode("22009")
+class InvalidTimeZoneDisplacementValue(DataError):
+    pass
+
+
+@sqlcode("2200B")
+class EscapeCharacterConflict(DataError):
+    pass
+
+
+@sqlcode("2200C")
+class InvalidUseOfEscapeCharacter(DataError):
+    pass
+
+
+@sqlcode("2200D")
+class InvalidEscapeOctet(DataError):
+    pass
+
+
+@sqlcode("2200F")
+class ZeroLengthCharacterString(DataError):
+    pass
+
+
+@sqlcode("2200G")
+class MostSpecificTypeMismatch(DataError):
+    pass
+
+
+@sqlcode("2200H")
+class SequenceGeneratorLimitExceeded(DataError):
+    pass
+
+
+@sqlcode("2200L")
+class NotAnXmlDocument(DataError):
+    pass
+
+
+@sqlcode("2200M")
+class InvalidXmlDocument(DataError):
+    pass
+
+
+@sqlcode("2200N")
+class InvalidXmlContent(DataError):
+    pass
+
+
+@sqlcode("2200S")
+class InvalidXmlComment(DataError):
+    pass
+
+
+@sqlcode("2200T")
+class InvalidXmlProcessingInstruction(DataError):
+    pass
+
+
+@sqlcode("22010")
+class InvalidIndicatorParameterValue(DataError):
+    pass
+
+
+@sqlcode("22011")
+class SubstringError(DataError):
+    pass
+
+
+@sqlcode("22012")
+class DivisionByZero(DataError):
+    pass
+
+
+@sqlcode("22013")
+class InvalidPrecedingOrFollowingSize(DataError):
+    pass
+
+
+@sqlcode("22014")
+class InvalidArgumentForNtileFunction(DataError):
+    pass
+
+
+@sqlcode("22015")
+class IntervalFieldOverflow(DataError):
+    pass
+
+
+@sqlcode("22016")
+class InvalidArgumentForNthValueFunction(DataError):
+    pass
+
+
+@sqlcode("22018")
+class InvalidCharacterValueForCast(DataError):
+    pass
+
+
+@sqlcode("22019")
+class InvalidEscapeCharacter(DataError):
+    pass
+
+
+@sqlcode("2201B")
+class InvalidRegularExpression(DataError):
+    pass
+
+
+@sqlcode("2201E")
+class InvalidArgumentForLogarithm(DataError):
+    pass
+
+
+@sqlcode("2201F")
+class InvalidArgumentForPowerFunction(DataError):
+    pass
+
+
+@sqlcode("2201G")
+class InvalidArgumentForWidthBucketFunction(DataError):
+    pass
+
+
+@sqlcode("2201W")
+class InvalidRowCountInLimitClause(DataError):
+    pass
+
+
+@sqlcode("2201X")
+class InvalidRowCountInResultOffsetClause(DataError):
+    pass
+
+
+@sqlcode("22021")
+class CharacterNotInRepertoire(DataError):
+    pass
+
+
+@sqlcode("22022")
+class IndicatorOverflow(DataError):
+    pass
+
+
+@sqlcode("22023")
+class InvalidParameterValue(DataError):
+    pass
+
+
+@sqlcode("22024")
+class UnterminatedCString(DataError):
+    pass
+
+
+@sqlcode("22025")
+class InvalidEscapeSequence(DataError):
+    pass
+
+
+@sqlcode("22026")
+class StringDataLengthMismatch(DataError):
+    pass
+
+
+@sqlcode("22027")
+class TrimError(DataError):
+    pass
+
+
+@sqlcode("2202E")
+class ArraySubscriptError(DataError):
+    pass
+
+
+@sqlcode("2202G")
+class InvalidTablesampleRepeat(DataError):
+    pass
+
+
+@sqlcode("2202H")
+class InvalidTablesampleArgument(DataError):
+    pass
+
+
+@sqlcode("22030")
+class DuplicateJsonObjectKeyValue(DataError):
+    pass
+
+
+@sqlcode("22032")
+class InvalidJsonText(DataError):
+    pass
+
+
+@sqlcode("22033")
+class InvalidSqlJsonSubscript(DataError):
+    pass
+
+
+@sqlcode("22034")
+class MoreThanOneSqlJsonItem(DataError):
+    pass
+
+
+@sqlcode("22035")
+class NoSqlJsonItem(DataError):
+    pass
+
+
+@sqlcode("22036")
+class NonNumericSqlJsonItem(DataError):
+    pass
+
+
+@sqlcode("22037")
+class NonUniqueKeysInAJsonObject(DataError):
+    pass
+
+
+@sqlcode("22038")
+class SingletonSqlJsonItemRequired(DataError):
+    pass
+
+
+@sqlcode("22039")
+class SqlJsonArrayNotFound(DataError):
+    pass
+
+
+@sqlcode("2203A")
+class SqlJsonMemberNotFound(DataError):
+    pass
+
+
+@sqlcode("2203B")
+class SqlJsonNumberNotFound(DataError):
+    pass
+
+
+@sqlcode("2203C")
+class SqlJsonObjectNotFound(DataError):
+    pass
+
+
+@sqlcode("2203D")
+class TooManyJsonArrayElements(DataError):
+    pass
+
+
+@sqlcode("2203E")
+class TooManyJsonObjectMembers(DataError):
+    pass
+
+
+@sqlcode("2203F")
+class SqlJsonScalarRequired(DataError):
+    pass
+
+
+@sqlcode("22P01")
+class FloatingPointException(DataError):
+    pass
+
+
+@sqlcode("22P02")
+class InvalidTextRepresentation(DataError):
+    pass
+
+
+@sqlcode("22P03")
+class InvalidBinaryRepresentation(DataError):
+    pass
+
+
+@sqlcode("22P04")
+class BadCopyFileFormat(DataError):
+    pass
+
+
+@sqlcode("22P05")
+class UntranslatableCharacter(DataError):
+    pass
+
+
+@sqlcode("22P06")
+class NonstandardUseOfEscapeCharacter(DataError):
+    pass
+
+
+# Class 23 - Integrity Constraint Violation
+
+
+@sqlcode("23000")
+class IntegrityConstraintViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23001")
+class RestrictViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23502")
+class NotNullViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23503")
+class ForeignKeyViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23505")
+class UniqueViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23514")
+class CheckViolation(IntegrityError):
+    pass
+
+
+@sqlcode("23P01")
+class ExclusionViolation(IntegrityError):
+    pass
+
+
+# Class 24 - Invalid Cursor State
+
+
+@sqlcode("24000")
+class InvalidCursorState(InternalError):
+    pass
+
+
+# Class 25 - Invalid Transaction State
+
+
+@sqlcode("25000")
+class InvalidTransactionState(InternalError):
+    pass
+
+
+@sqlcode("25001")
+class ActiveSqlTransaction(InternalError):
+    pass
+
+
+@sqlcode("25002")
+class BranchTransactionAlreadyActive(InternalError):
+    pass
+
+
+@sqlcode("25003")
+class InappropriateAccessModeForBranchTransaction(InternalError):
+    pass
+
+
+@sqlcode("25004")
+class InappropriateIsolationLevelForBranchTransaction(InternalError):
+    pass
+
+
+@sqlcode("25005")
+class NoActiveSqlTransactionForBranchTransaction(InternalError):
+    pass
+
+
+@sqlcode("25006")
+class ReadOnlySqlTransaction(InternalError):
+    pass
+
+
+@sqlcode("25007")
+class SchemaAndDataStatementMixingNotSupported(InternalError):
+    pass
+
+
+@sqlcode("25008")
+class HeldCursorRequiresSameIsolationLevel(InternalError):
+    pass
+
+
+@sqlcode("25P01")
+class NoActiveSqlTransaction(InternalError):
+    pass
+
+
+@sqlcode("25P02")
+class InFailedSqlTransaction(InternalError):
+    pass
+
+
+@sqlcode("25P03")
+class IdleInTransactionSessionTimeout(InternalError):
+    pass
+
+
+# Class 26 - Invalid SQL Statement Name
+
+
+@sqlcode("26000")
+class InvalidSqlStatementName(ProgrammingError):
+    pass
+
+
+# Class 27 - Triggered Data Change Violation
+
+
+@sqlcode("27000")
+class TriggeredDataChangeViolation(OperationalError):
+    pass
+
+
+# Class 28 - Invalid Authorization Specification
+
+
+@sqlcode("28000")
+class InvalidAuthorizationSpecification(OperationalError):
+    pass
+
+
+@sqlcode("28P01")
+class InvalidPassword(OperationalError):
+    pass
+
+
+# Class 2B - Dependent Privilege Descriptors Still Exist
+
+
+@sqlcode("2B000")
+class DependentPrivilegeDescriptorsStillExist(InternalError):
+    pass
+
+
+@sqlcode("2BP01")
+class DependentObjectsStillExist(InternalError):
+    pass
+
+
+# Class 2D - Invalid Transaction Termination
+
+
+@sqlcode("2D000")
+class InvalidTransactionTermination(InternalError):
+    pass
+
+
+# Class 2F - SQL Routine Exception
+
+
+@sqlcode("2F000")
+class SqlRoutineException(OperationalError):
+    pass
+
+
+@sqlcode("2F002")
+class ModifyingSqlDataNotPermitted(OperationalError):
+    pass
+
+
+@sqlcode("2F003")
+class ProhibitedSqlStatementAttempted(OperationalError):
+    pass
+
+
+@sqlcode("2F004")
+class ReadingSqlDataNotPermitted(OperationalError):
+    pass
+
+
+@sqlcode("2F005")
+class FunctionExecutedNoReturnStatement(OperationalError):
+    pass
+
+
+# Class 34 - Invalid Cursor Name
+
+
+@sqlcode("34000")
+class InvalidCursorName(ProgrammingError):
+    pass
+
+
+# Class 38 - External Routine Exception
+
+
+@sqlcode("38000")
+class ExternalRoutineException(OperationalError):
+    pass
+
+
+@sqlcode("38001")
+class ContainingSqlNotPermitted(OperationalError):
+    pass
+
+
+@sqlcode("38002")
+class ModifyingSqlDataNotPermittedExt(OperationalError):
+    pass
+
+
+@sqlcode("38003")
+class ProhibitedSqlStatementAttemptedExt(OperationalError):
+    pass
+
+
+@sqlcode("38004")
+class ReadingSqlDataNotPermittedExt(OperationalError):
+    pass
+
+
+# Class 39 - External Routine Invocation Exception
+
+
+@sqlcode("39000")
+class ExternalRoutineInvocationException(OperationalError):
+    pass
+
+
+@sqlcode("39001")
+class InvalidSqlstateReturned(OperationalError):
+    pass
+
+
+@sqlcode("39004")
+class NullValueNotAllowedExt(OperationalError):
+    pass
+
+
+@sqlcode("39P01")
+class TriggerProtocolViolated(OperationalError):
+    pass
+
+
+@sqlcode("39P02")
+class SrfProtocolViolated(OperationalError):
+    pass
+
+
+@sqlcode("39P03")
+class EventTriggerProtocolViolated(OperationalError):
+    pass
+
+
+# Class 3B - Savepoint Exception
+
+
+@sqlcode("3B000")
+class SavepointException(OperationalError):
+    pass
+
+
+@sqlcode("3B001")
+class InvalidSavepointSpecification(OperationalError):
+    pass
+
+
+# Class 3D - Invalid Catalog Name
+
+
+@sqlcode("3D000")
+class InvalidCatalogName(ProgrammingError):
+    pass
+
+
+# Class 3F - Invalid Schema Name
+
+
+@sqlcode("3F000")
+class InvalidSchemaName(ProgrammingError):
+    pass
+
+
+# Class 40 - Transaction Rollback
+
+
+@sqlcode("40000")
+class TransactionRollback(OperationalError):
+    pass
+
+
+@sqlcode("40001")
+class SerializationFailure(OperationalError):
+    pass
+
+
+@sqlcode("40002")
+class TransactionIntegrityConstraintViolation(OperationalError):
+    pass
+
+
+@sqlcode("40003")
+class StatementCompletionUnknown(OperationalError):
+    pass
+
+
+@sqlcode("40P01")
+class DeadlockDetected(OperationalError):
+    pass
+
+
+# Class 42 - Syntax Error or Access Rule Violation
+
+
+@sqlcode("42000")
+class SyntaxErrorOrAccessRuleViolation(ProgrammingError):
+    pass
+
+
+@sqlcode("42501")
+class InsufficientPrivilege(ProgrammingError):
+    pass
+
+
+@sqlcode("42601")
+class SyntaxError(ProgrammingError):
+    pass
+
+
+@sqlcode("42602")
+class InvalidName(ProgrammingError):
+    pass
+
+
+@sqlcode("42611")
+class InvalidColumnDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42622")
+class NameTooLong(ProgrammingError):
+    pass
+
+
+@sqlcode("42701")
+class DuplicateColumn(ProgrammingError):
+    pass
+
+
+@sqlcode("42702")
+class AmbiguousColumn(ProgrammingError):
+    pass
+
+
+@sqlcode("42703")
+class UndefinedColumn(ProgrammingError):
+    pass
+
+
+@sqlcode("42704")
+class UndefinedObject(ProgrammingError):
+    pass
+
+
+@sqlcode("42710")
+class DuplicateObject(ProgrammingError):
+    pass
+
+
+@sqlcode("42712")
+class DuplicateAlias(ProgrammingError):
+    pass
+
+
+@sqlcode("42723")
+class DuplicateFunction(ProgrammingError):
+    pass
+
+
+@sqlcode("42725")
+class AmbiguousFunction(ProgrammingError):
+    pass
+
+
+@sqlcode("42803")
+class GroupingError(ProgrammingError):
+    pass
+
+
+@sqlcode("42804")
+class DatatypeMismatch(ProgrammingError):
+    pass
+
+
+@sqlcode("42809")
+class WrongObjectType(ProgrammingError):
+    pass
+
+
+@sqlcode("42830")
+class InvalidForeignKey(ProgrammingError):
+    pass
+
+
+@sqlcode("42846")
+class CannotCoerce(ProgrammingError):
+    pass
+
+
+@sqlcode("42883")
+class UndefinedFunction(ProgrammingError):
+    pass
+
+
+@sqlcode("428C9")
+class GeneratedAlways(ProgrammingError):
+    pass
+
+
+@sqlcode("42939")
+class ReservedName(ProgrammingError):
+    pass
+
+
+@sqlcode("42P01")
+class UndefinedTable(ProgrammingError):
+    pass
+
+
+@sqlcode("42P02")
+class UndefinedParameter(ProgrammingError):
+    pass
+
+
+@sqlcode("42P03")
+class DuplicateCursor(ProgrammingError):
+    pass
+
+
+@sqlcode("42P04")
+class DuplicateDatabase(ProgrammingError):
+    pass
+
+
+@sqlcode("42P05")
+class DuplicatePreparedStatement(ProgrammingError):
+    pass
+
+
+@sqlcode("42P06")
+class DuplicateSchema(ProgrammingError):
+    pass
+
+
+@sqlcode("42P07")
+class DuplicateTable(ProgrammingError):
+    pass
+
+
+@sqlcode("42P08")
+class AmbiguousParameter(ProgrammingError):
+    pass
+
+
+@sqlcode("42P09")
+class AmbiguousAlias(ProgrammingError):
+    pass
+
+
+@sqlcode("42P10")
+class InvalidColumnReference(ProgrammingError):
+    pass
+
+
+@sqlcode("42P11")
+class InvalidCursorDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P12")
+class InvalidDatabaseDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P13")
+class InvalidFunctionDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P14")
+class InvalidPreparedStatementDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P15")
+class InvalidSchemaDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P16")
+class InvalidTableDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P17")
+class InvalidObjectDefinition(ProgrammingError):
+    pass
+
+
+@sqlcode("42P18")
+class IndeterminateDatatype(ProgrammingError):
+    pass
+
+
+@sqlcode("42P19")
+class InvalidRecursion(ProgrammingError):
+    pass
+
+
+@sqlcode("42P20")
+class WindowingError(ProgrammingError):
+    pass
+
+
+@sqlcode("42P21")
+class CollationMismatch(ProgrammingError):
+    pass
+
+
+@sqlcode("42P22")
+class IndeterminateCollation(ProgrammingError):
+    pass
+
+
+# Class 44 - WITH CHECK OPTION Violation
+
+
+@sqlcode("44000")
+class WithCheckOptionViolation(ProgrammingError):
+    pass
+
+
+# Class 53 - Insufficient Resources
+
+
+@sqlcode("53000")
+class InsufficientResources(OperationalError):
+    pass
+
+
+@sqlcode("53100")
+class DiskFull(OperationalError):
+    pass
+
+
+@sqlcode("53200")
+class OutOfMemory(OperationalError):
+    pass
+
+
+@sqlcode("53300")
+class TooManyConnections(OperationalError):
+    pass
+
+
+@sqlcode("53400")
+class ConfigurationLimitExceeded(OperationalError):
+    pass
+
+
+# Class 54 - Program Limit Exceeded
+
+
+@sqlcode("54000")
+class ProgramLimitExceeded(OperationalError):
+    pass
+
+
+@sqlcode("54001")
+class StatementTooComplex(OperationalError):
+    pass
+
+
+@sqlcode("54011")
+class TooManyColumns(OperationalError):
+    pass
+
+
+@sqlcode("54023")
+class TooManyArguments(OperationalError):
+    pass
+
+
+# Class 55 - Object Not In Prerequisite State
+
+
+@sqlcode("55000")
+class ObjectNotInPrerequisiteState(OperationalError):
+    pass
+
+
+@sqlcode("55006")
+class ObjectInUse(OperationalError):
+    pass
+
+
+@sqlcode("55P02")
+class CantChangeRuntimeParam(OperationalError):
+    pass
+
+
+@sqlcode("55P03")
+class LockNotAvailable(OperationalError):
+    pass
+
+
+@sqlcode("55P04")
+class UnsafeNewEnumValueUsage(OperationalError):
+    pass
+
+
+# Class 57 - Operator Intervention
+
+
+@sqlcode("57000")
+class OperatorIntervention(OperationalError):
+    pass
+
+
+@sqlcode("57014")
+class QueryCanceled(OperationalError):
+    pass
+
+
+@sqlcode("57P01")
+class AdminShutdown(OperationalError):
+    pass
+
+
+@sqlcode("57P02")
+class CrashShutdown(OperationalError):
+    pass
+
+
+@sqlcode("57P03")
+class CannotConnectNow(OperationalError):
+    pass
+
+
+@sqlcode("57P04")
+class DatabaseDropped(OperationalError):
+    pass
+
+
+# Class 58 - System Error (errors external to PostgreSQL itself)
+
+
+@sqlcode("58000")
+class SystemError(OperationalError):
+    pass
+
+
+@sqlcode("58030")
+class IoError(OperationalError):
+    pass
+
+
+@sqlcode("58P01")
+class UndefinedFile(OperationalError):
+    pass
+
+
+@sqlcode("58P02")
+class DuplicateFile(OperationalError):
+    pass
+
+
+# Class 72 - Snapshot Failure
+
+
+@sqlcode("72000")
+class SnapshotTooOld(DatabaseError):
+    pass
+
+
+# Class F0 - Configuration File Error
+
+
+@sqlcode("F0000")
+class ConfigFileError(OperationalError):
+    pass
+
+
+@sqlcode("F0001")
+class LockFileExists(OperationalError):
+    pass
+
+
+# Class HV - Foreign Data Wrapper Error (SQL/MED)
+
+
+@sqlcode("HV000")
+class FdwError(OperationalError):
+    pass
+
+
+@sqlcode("HV001")
+class FdwOutOfMemory(OperationalError):
+    pass
+
+
+@sqlcode("HV002")
+class FdwDynamicParameterValueNeeded(OperationalError):
+    pass
+
+
+@sqlcode("HV004")
+class FdwInvalidDataType(OperationalError):
+    pass
+
+
+@sqlcode("HV005")
+class FdwColumnNameNotFound(OperationalError):
+    pass
+
+
+@sqlcode("HV006")
+class FdwInvalidDataTypeDescriptors(OperationalError):
+    pass
+
+
+@sqlcode("HV007")
+class FdwInvalidColumnName(OperationalError):
+    pass
+
+
+@sqlcode("HV008")
+class FdwInvalidColumnNumber(OperationalError):
+    pass
+
+
+@sqlcode("HV009")
+class FdwInvalidUseOfNullPointer(OperationalError):
+    pass
+
+
+@sqlcode("HV00A")
+class FdwInvalidStringFormat(OperationalError):
+    pass
+
+
+@sqlcode("HV00B")
+class FdwInvalidHandle(OperationalError):
+    pass
+
+
+@sqlcode("HV00C")
+class FdwInvalidOptionIndex(OperationalError):
+    pass
+
+
+@sqlcode("HV00D")
+class FdwInvalidOptionName(OperationalError):
+    pass
+
+
+@sqlcode("HV00J")
+class FdwOptionNameNotFound(OperationalError):
+    pass
+
+
+@sqlcode("HV00K")
+class FdwReplyHandle(OperationalError):
+    pass
+
+
+@sqlcode("HV00L")
+class FdwUnableToCreateExecution(OperationalError):
+    pass
+
+
+@sqlcode("HV00M")
+class FdwUnableToCreateReply(OperationalError):
+    pass
+
+
+@sqlcode("HV00N")
+class FdwUnableToEstablishConnection(OperationalError):
+    pass
+
+
+@sqlcode("HV00P")
+class FdwNoSchemas(OperationalError):
+    pass
+
+
+@sqlcode("HV00Q")
+class FdwSchemaNotFound(OperationalError):
+    pass
+
+
+@sqlcode("HV00R")
+class FdwTableNotFound(OperationalError):
+    pass
+
+
+@sqlcode("HV010")
+class FdwFunctionSequenceError(OperationalError):
+    pass
+
+
+@sqlcode("HV014")
+class FdwTooManyHandles(OperationalError):
+    pass
+
+
+@sqlcode("HV021")
+class FdwInconsistentDescriptorInformation(OperationalError):
+    pass
+
+
+@sqlcode("HV024")
+class FdwInvalidAttributeValue(OperationalError):
+    pass
+
+
+@sqlcode("HV090")
+class FdwInvalidStringLengthOrBufferLength(OperationalError):
+    pass
+
+
+@sqlcode("HV091")
+class FdwInvalidDescriptorFieldIdentifier(OperationalError):
+    pass
+
+
+# Class P0 - PL/pgSQL Error
+
+
+@sqlcode("P0000")
+class PlpgsqlError(ProgrammingError):
+    pass
+
+
+@sqlcode("P0001")
+class RaiseException(ProgrammingError):
+    pass
+
+
+@sqlcode("P0002")
+class NoDataFound(ProgrammingError):
+    pass
+
+
+@sqlcode("P0003")
+class TooManyRows(ProgrammingError):
+    pass
+
+
+@sqlcode("P0004")
+class AssertFailure(ProgrammingError):
+    pass
+
+
+# Class XX - Internal Error
+
+
+@sqlcode("XX000")
+class InternalError_(InternalError):
+    pass
+
+
+@sqlcode("XX001")
+class DataCorrupted(InternalError):
+    pass
+
+
+@sqlcode("XX002")
+class IndexCorrupted(InternalError):
+    pass
+
+
+# autogenerated: end
index c6429cbe51cee99a8bca38cef292f04506b2101e..a1fa6c24a3a502e250678594303278606c855bef 100644 (file)
@@ -73,3 +73,33 @@ def test_error_encoding(conn, enc):
     diag = excinfo.value.diag
     assert f'"{eur}"' in diag.message_primary
     assert diag.sqlstate == "42P01"
+
+
+def test_exception_class(conn):
+    cur = conn.cursor()
+
+    with pytest.raises(e.DatabaseError) as excinfo:
+        cur.execute("select * from nonexist")
+
+    assert isinstance(excinfo.value, e.UndefinedTable)
+    assert isinstance(excinfo.value, conn.ProgrammingError)
+
+
+def test_exception_class_fallback(conn):
+    cur = conn.cursor()
+
+    x = e._sqlcodes.pop("42P01")
+    try:
+        with pytest.raises(e.Error) as excinfo:
+            cur.execute("select * from nonexist")
+    finally:
+        e._sqlcodes["42P01"] = x
+
+    assert type(excinfo.value) is conn.ProgrammingError
+
+
+def test_lookup():
+    assert e.lookup("42P01") is e.UndefinedTable
+
+    with pytest.raises(KeyError):
+        e.lookup("XXXXX")
diff --git a/tools/update_errors.py b/tools/update_errors.py
new file mode 100755 (executable)
index 0000000..83b8b9a
--- /dev/null
@@ -0,0 +1,150 @@
+#!/usr/bin/env python
+"""Generate per-sqlstate errors from PostgreSQL source code.
+
+The script can be run at a new PostgreSQL release to refresh the module.
+"""
+
+# Copyright (C) 2020 The Psycopg Team
+
+
+import os
+import re
+import sys
+from urllib.request import urlopen
+from collections import defaultdict
+
+from psycopg3.errors import get_base_exception
+
+
+def main():
+
+    fn = os.path.dirname(__file__) + "/../psycopg3/errors.py"
+
+    with open(fn, "r") as f:
+        lines = f.read().splitlines()
+
+    istart, iend = [
+        i
+        for i, line in enumerate(lines)
+        if re.match(r"\s*#\s*autogenerated:\s+(start|end)", line)
+    ]
+
+    classes, errors = fetch_errors(["9.5", "9.6", "10", "11", "12"])
+    lines[istart + 1 : iend] = generate_module_data(classes, errors)
+
+    with open(fn, "w") as f:
+        for line in lines:
+            f.write(line + "\n")
+
+
+def parse_errors_txt(url):
+    classes = {}
+    errors = defaultdict(dict)
+
+    page = urlopen(url)
+    for line in page.read().decode("ascii").splitlines():
+        # 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 = """
+@sqlcode(%(errcode)r)
+class %(cls)s(%(base)s):
+    pass
+"""
+    specific = {
+        "38002": "ModifyingSqlDataNotPermittedExt",
+        "38003": "ProhibitedSqlStatementAttemptedExt",
+        "38004": "ReadingSqlDataNotPermittedExt",
+        "39004": "NullValueNotAllowedExt",
+        "XX000": "InternalError_",
+    }
+
+    seen = set(
+        """
+        Error Warning InterfaceError DataError DatabaseError ProgrammingError
+        IntegrityError InternalError NotSupportedError OperationalError
+        """.split()
+    )
+
+    for clscode, clslabel in sorted(classes.items()):
+        if clscode in ("00", "01"):
+            # success and warning - never raised
+            continue
+
+        yield f"\n# {clslabel}"
+
+        for errcode, errlabel in sorted(errors[clscode].items()):
+            if errcode in specific:
+                clsname = specific[errcode]
+            else:
+                clsname = errlabel.title().replace("_", "")
+            if clsname in seen:
+                raise Exception("class already existing: %s" % clsname)
+            seen.add(clsname)
+
+            base = get_base_exception(errcode)
+
+            yield tmpl % {
+                "cls": clsname,
+                "errcode": errcode,
+                "base": base.__name__,
+            }
+
+
+if __name__ == "__main__":
+    sys.exit(main())