From e3b0d246a48c49bae3dcfce22479511a7177edce Mon Sep 17 00:00:00 2001 From: Daniele Varrazzo Date: Sun, 31 May 2020 09:14:50 +1200 Subject: [PATCH] Added mapping from sqlstate to error classes --- psycopg3/errors.py | 1447 +++++++++++++++++++++++++++++++++++++++- tests/test_errors.py | 30 + tools/update_errors.py | 150 +++++ 3 files changed, 1622 insertions(+), 5 deletions(-) create mode 100755 tools/update_errors.py diff --git a/psycopg3/errors.py b/psycopg3/errors.py index 3f4d8c0fe..c16f7390b 100644 --- a/psycopg3/errors.py +++ b/psycopg3/errors.py @@ -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 diff --git a/tests/test_errors.py b/tests/test_errors.py index c6429cbe5..a1fa6c24a 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -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 index 000000000..83b8b9aee --- /dev/null +++ b/tools/update_errors.py @@ -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()) -- 2.47.2