From a00843751e8f51fa2adf4a39498533492c692342 Mon Sep 17 00:00:00 2001 From: Steven Fackler Date: Sat, 14 Sep 2013 21:37:20 -0700 Subject: [PATCH] Parse Notice/Error SQLSTATE codes The enum is stuck inside of an extra module due to mozilla/rust#4375. --- Makefile | 2 +- src/error.rs | 342 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/lib.rs | 13 +- src/test.rs | 13 +- 4 files changed, 356 insertions(+), 14 deletions(-) create mode 100644 src/error.rs diff --git a/Makefile b/Makefile index 107b1443..1d711d1f 100644 --- a/Makefile +++ b/Makefile @@ -4,7 +4,7 @@ RUSTFLAGS += -L. --cfg debug -Z debug-info .PHONY: all all: postgres.dummy -postgres.dummy: src/lib.rs src/message.rs src/types.rs +postgres.dummy: src/lib.rs src/message.rs src/types.rs src/error.rs $(RUSTC) $(RUSTFLAGS) --lib src/lib.rs --out-dir . touch $@ diff --git a/src/error.rs b/src/error.rs new file mode 100644 index 00000000..c9b4cf72 --- /dev/null +++ b/src/error.rs @@ -0,0 +1,342 @@ +macro_rules! make_errors( + ($($code:pat => $error:ident),+) => ( + // TODO: Get rid of this module when mozilla/rust#4375 is fixed + mod hack { + #[deriving(ToStr, Eq)] + pub enum PostgresSqlState { + $($error),+ , + UnknownSqlState(~str) + } + + impl FromStr for PostgresSqlState { + fn from_str(s: &str) -> Option { + Some(match s { + $($code => $error),+ , + state => UnknownSqlState(state.to_owned()) + }) + } + } + } + ) +) + +// From http://www.postgresql.org/docs/9.2/static/errcodes-appendix.html +make_errors!( + // Class 00 — Successful Completion + "00000" => SuccessfulCompletion, + + // Class 01 — Warning + "01000" => Warning, + "0100C" => DynamicResultSetsReturned, + "01008" => ImplicitZeroBitPadding, + "01003" => NullValueEliminatedInSetFunction, + "01007" => PrivilegeNotGranted, + "01006" => PrivilegeNotRevoked, + "01004" => StringDataRightTruncationWarning, + "01P01" => DeprecatedFeature, + + // Class 02 — No Data + "02000" => NoData, + "02001" => NoAdditionalDynamicResultSetsReturned, + + // Class 03 — SQL Statement Not Yet Complete + "03000" => SqlStatementNotYetComplete, + + // Class 08 — Connection Exception + "08000" => ConnectionException, + "08003" => ConnectionDoesNotExist, + "08006" => ConnectionFailure, + "08001" => SqlclientUnableToEstablishSqlconnection, + "08004" => SqlserverRejectedEstablishmentOfSqlconnection, + "08007" => TransactionResolutionUnknown, + "08P01" => ProtocolViolation, + + // Class 09 — Triggered Action Exception + "09000" => TriggeredActionException, + + // Class 0A — Feature Not Supported + "0A000" => FeatureNotSupported, + + // Class 0B — Invalid Transaction Initiation + "0B000" => InvalidTransactionInitiation, + + // Class 0F — Locator Exception + "0F000" => LocatorException, + "0F001" => InvalidLocatorException, + + // Class 0L — Invalid Grantor + "0L000" => InvalidGrantor, + "0LP01" => InvalidGrantOperation, + + // Class 0P — Invalid Role Specification + "0P000" => InvalidRoleSpecification, + + // Class 0Z — Diagnostics Exception + "0Z000" => DiagnosticsException, + "0Z002" => StackedDiagnosticsAccessedWithoutActiveHandler, + + // Class 20 — Case Not Found + "20000" => CaseNotFound, + + // Class 21 — Cardinality Violation + "21000" => CardinalityViolation, + + // Class 22 — Data Exception + "22000" => DataException, + "2202E" => ArraySubscriptError, + "22021" => CharacterNotInRepertoire, + "22008" => DatetimeFieldOverflow, + "22012" => DivisionByZero, + "22005" => ErrorInAssignment, + "2200B" => EscapeCharacterConflict, + "22022" => IndicatorOverflow, + "22015" => IntervalFieldOverflow, + "2201E" => InvalidArgumentForLogarithm, + "22014" => InvalidArgumentForNtileFunction, + "22016" => InvalidArgumentForNthValueFunction, + "2201F" => InvalidArgumentForPowerFunction, + "2201G" => InvalidArgumentForWidthBucketFunction, + "22018" => InvalidCharacterValueForCast, + "22007" => InvalidDatetimeFormat, + "22019" => InvalidEscapeCharacter, + "2200D" => InvalidEscapeOctet, + "22025" => InvalidEscapeSequence, + "22P06" => NonstandardUseOfEscapeCharacter, + "22010" => InvalidIndicatorParameterValue, + "22023" => InvalidParameterValue, + "2201B" => InvalidRegularExpression, + "2201W" => InvalidRowCountInLimitClause, + "2201X" => InvalidRowCountInResultOffsetClause, + "22009" => InvalidTimeZoneDisplacementValue, + "2200C" => InvalidUseOfEscapeCharacter, + "2200G" => MostSpecificTypeMismatch, + "22004" => NullValueNotAllowedData, + "22002" => NullValueNoIndicatorParameter, + "22003" => NumericValueOutOfRange, + "22026" => StringDataLengthMismatch, + "22001" => StringDataRightTruncationException, + "22011" => SubstringError, + "22027" => TrimError, + "22024" => UnterminatedCString, + "2200F" => ZeroLengthCharacterString, + "22P01" => FloatingPointException, + "22P02" => InvalidTextRepresentation, + "22P03" => InvalidBinaryRepresentation, + "22P04" => BadCopyFileFormat, + "22P05" => UntranslatableCharacter, + "2200L" => NotAnXmlDocument, + "2200M" => InvalidXmlDocument, + "2200N" => InvalidXmlContent, + "2200S" => InvalidXmlComment, + "2200T" => InvalidXmlProcessingInstruction, + + // Class 23 — Integrity Constraint Violation + "23000" => IntegrityConstraintViolation, + "23001" => RestrictViolation, + "23502" => NotNullViolation, + "23503" => ForeignKeyViolation, + "23505" => UniqueViolation, + "23514" => CheckViolation, + "32P01" => ExclusionViolation, + + // Class 24 — Invalid Cursor State + "24000" => InvalidCursorState, + + // Class 25 — Invalid Transaction State + "25000" => InvalidTransactionState, + "25001" => ActiveSqlTransaction, + "25002" => BranchTransactionAlreadyActive, + "25008" => HeldCursorRequiresSameIsolationLevel, + "25003" => InappropriateAccessModeForBranchTransaction, + "25004" => InappropriateIsolationLevelForBranchTransaction, + "25005" => NoActiveSqlTransactionForBranchTransaction, + "25006" => ReadOnlySqlTransaction, + "25007" => SchemaAndDataStatementMixingNotSupported, + "25P01" => NoActiveSqlTransaction, + "25P02" => InFailedSqlTransaction, + + // Class 26 — Invalid SQL Statement Name + "26000" => InvalidSqlStatementName, + + // Class 27 — Triggered Data Change Violation + "27000" => TriggeredDataChangeViolation, + + // Class 28 — Invalid Authorization Specification + "28000" => InvalidAuthorizationSpecification, + "28P01" => InvalidPassword, + + // Class 2B — Dependent Privilege Descriptors Still Exist + "2B000" => DependentPrivilegeDescriptorsStillExist, + "2BP01" => DependentObjectsStillExist, + + // Class 2D — Invalid Transaction Termination + "2D000" => InvalidTransactionTermination, + + // Class 2F — SQL Routine Exception + "2F000" => SqlRoutineException, + "2F005" => FunctionExecutedNoReturnStatement, + "2F002" => ModifyingSqlDataNotPermittedSqlRoutine, + "2F003" => ProhibitedSqlStatementAttemptedSqlRoutine, + "2F004" => ReadingSqlDataNotPermittedSqlRoutine, + + // Class 34 — Invalid Cursor Name + "34000" => InvalidCursorName, + + // Class 38 — External Routine Exception + "38000" => ExternalRoutineException, + "38001" => ContainingSqlNotPermitted, + "38002" => ModifyingSqlDataNotPermittedExternalRoutine, + "38003" => ProhibitedSqlStatementAttemptedExternalRoutine, + "38004" => ReadingSqlDataNotPermittedExternalRoutine, + + + // Class 39 — External Routine Invocation Exception + "39000" => ExternalRoutineInvocationException, + "39001" => InvalidSqlstateReturned, + "39004" => NullValueNotAllowedExternalRoutine, + "39P01" => TriggerProtocolViolated, + "39P02" => SrfProtocolViolated, + + // Class 3B — Savepoint Exception + "3B000" => SavepointException, + "3B001" => InvalidSavepointException, + + // Class 3D — Invalid Catalog Name + "3D000" => InvalidCatalogName, + + // Class 3F — Invalid Schema Name + "3F000" => InvalidSchemaName, + + // Class 40 — Transaction Rollback + "40000" => TransactionRollback, + "40002" => TransactionIntegrityConstraintViolation, + "40001" => SerializationFailure, + "40003" => StatementCompletionUnknown, + "40P01" => DeadlockDetected, + + // Class 42 — Syntax Error or Access Rule Violation + "42000" => SyntaxErrorOrAccessRuleViolation, + "42601" => SyntaxError, + "42501" => InsufficientPrivilege, + "42846" => CannotCoerce, + "42803" => GroupingError, + "42P20" => WindowingError, + "42P19" => InvalidRecursion, + "42830" => InvalidForeignKey, + "42602" => InvalidName, + "42622" => NameTooLong, + "42939" => ReservedName, + "42804" => DatatypeMismatch, + "42P18" => IndeterminateDatatype, + "42P21" => CollationMismatch, + "42P22" => IndeterminateCollation, + "42809" => WrongObjectType, + "42703" => UndefinedColumn, + "42883" => UndefinedFunction, + "42P01" => UndefinedTable, + "42P02" => UndefinedParameter, + "42704" => UndefinedObject, + "42701" => DuplicateColumn, + "42P03" => DuplicateCursor, + "42P04" => DuplicateDatabase, + "42723" => DuplicateFunction, + "42P05" => DuplicatePreparedStatement, + "42P06" => DuplicateSchema, + "42P07" => DuplicateTable, + "42712" => DuplicateAliaas, + "42710" => DuplicateObject, + "42702" => AmbiguousColumn, + "42725" => AmbiguousFunction, + "42P08" => AmbiguousParameter, + "42P09" => AmbiguousAlias, + "42P10" => InvalidColumnReference, + "42611" => InvalidColumnDefinition, + "42P11" => InvalidCursorDefinition, + "42P12" => InvalidDatabaseDefinition, + "42P13" => InvalidFunctionDefinition, + "42P14" => InvalidPreparedStatementDefinition, + "42P15" => InvalidSchemaDefinition, + "42P16" => InvalidTableDefinition, + "42P17" => InvalidObjectDefinition, + + // Class 44 — WITH CHECK OPTION Violation + "44000" => WithCheckOptionViolation, + + // Class 53 — Insufficient Resources + "53000" => InsufficientResources, + "53100" => DiskFull, + "53200" => OutOfMemory, + "53300" => TooManyConnections, + "53400" => ConfigurationLimitExceeded, + + // Class 54 — Program Limit Exceeded + "54000" => ProgramLimitExceeded, + "54001" => StatementTooComplex, + "54011" => TooManyColumns, + "54023" => TooManyArguments, + + // Class 55 — Object Not In Prerequisite State + "55000" => ObjectNotInPrerequisiteState, + "55006" => ObjectInUse, + "55P02" => CantChangeRuntimeParam, + "55P03" => LockNotAvailable, + + // Class 57 — Operator Intervention + "57000" => OperatorIntervention, + "57014" => QueryCanceled, + "57P01" => AdminShutdown, + "57P02" => CrashShutdown, + "57P03" => CannotConnectNow, + "57P04" => DatabaseDropped, + + // Class 58 — System Error + "58000" => SystemError, + "58030" => IoError, + "58P01" => UndefinedFile, + "58P02" => DuplicateFile, + + // Class F0 — Configuration File Error + "F0000" => ConfigFileError, + "F0001" => LockFileExists, + + // Class HV — Foreign Data Wrapper Error (SQL/MED) + "HV000" => FdwError, + "HV005" => FdwColumnNameNotFound, + "HV002" => FdwDynamicParameterValueNeeded, + "HV010" => FdwFunctionSequenceError, + "HV021" => FdwInconsistentDescriptorInformation, + "HV024" => FdwInvalidAttributeValue, + "HV007" => FdwInvalidColumnName, + "HV008" => FdwInvalidColumnNumber, + "HV004" => FdwInvalidDataType, + "HV006" => FdwInvalidDataTypeDescriptors, + "HV091" => FdwInvalidDescriptorFieldIdentifier, + "HV00B" => FdwInvalidHandle, + "HV00C" => FdwInvalidOptionIndex, + "HV00D" => FdwInvalidOptionName, + "HV090" => FdwInvalidStringLengthOrBufferLength, + "HV00A" => FdwInvalidStringFormat, + "HV009" => FdwInvalidUseOfNullPointer, + "HV014" => FdwTooManyHandles, + "HV001" => FdwOutOfMemory, + "HV00P" => FdwNoSchemas, + "HV00J" => FdwOptionNameNotFound, + "HV00K" => FdwReplyHandle, + "HV00Q" => FdwSchemaNotFound, + "HV00R" => FdwTableNotFound, + "HV00L" => FdwUnableToCreateExcecution, + "HV00M" => FdwUnableToCreateReply, + "HV00N" => FdwUnableToEstablishConnection, + + // Class P0 — PL/pgSQL Error + "P0000" => PlpgsqlError, + "P0001" => RaiseException, + "P0002" => NoDataFound, + "P0003" => TooManyRows, + + // Class XX — Internal Error + "XX000" => InternalError, + "XX001" => DataCorrupted, + "XX002" => IndexCorrupted +) + diff --git a/src/lib.rs b/src/lib.rs index ea04a8e1..ce3b31e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -18,6 +18,8 @@ use std::rt::io::net::ip; use std::rt::io::net::ip::SocketAddr; use std::rt::io::net::tcp::TcpStream; +use error::hack::PostgresSqlState; + use message::{BackendMessage, AuthenticationOk, AuthenticationKerberosV5, @@ -55,6 +57,7 @@ use message::{RowDescriptionEntry, WriteMessage, ReadMessage}; use types::{PostgresType, ToSql, FromSql}; +mod error; mod message; mod types; @@ -94,9 +97,10 @@ pub enum PostgresErrorPosition { pub struct PostgresDbError { // This could almost be an enum, except the values can be localized :( severity: ~str, - // Should probably end up as an enum - code: ~str, + code: PostgresSqlState, message: ~str, + detail: Option<~str>, + hint: Option<~str>, position: Option, where: Option<~str>, file: ~str, @@ -110,8 +114,10 @@ impl PostgresDbError { let mut map: HashMap = fields.move_rev_iter().collect(); PostgresDbError { severity: map.pop(&('S' as u8)).unwrap(), - code: map.pop(&('C' as u8)).unwrap(), + code: FromStr::from_str(map.pop(&('C' as u8)).unwrap()).unwrap(), message: map.pop(&('M' as u8)).unwrap(), + detail: map.pop(&('D' as u8)), + hint: map.pop(&('H' as u8)), position: match map.pop(&('P' as u8)) { Some(pos) => Some(Position(FromStr::from_str(pos).unwrap())), None => match map.pop(&('p' as u8)) { @@ -386,7 +392,6 @@ impl PostgresConnection { }) } - pub fn in_transaction(&self, blk: &fn(&PostgresTransaction) -> T) -> T { self.quick_query("BEGIN"); diff --git a/src/test.rs b/src/test.rs index ece5f164..82d74d8a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -17,19 +17,14 @@ use postgres::{PostgresNoticeHandler, PostgresDbError, PostgresStatement, ResultDescription}; +use postgres::error::hack::{SyntaxError, InvalidPassword}; use postgres::types::{ToSql, FromSql, PgInt4, PgVarchar}; #[test] fn test_prepare_err() { let conn = PostgresConnection::connect("postgres://postgres@localhost"); match conn.try_prepare("invalid sql statment") { - Err(PostgresDbError { position, code, _ }) => { - assert_eq!(code, ~"42601"); - match position { - Some(Position(1)) => (), - position => fail!("Unexpected position %?", position) - } - } + Err(PostgresDbError { code: SyntaxError, position: Some(Position(1)), _ }) => (), resp => fail!("Unexpected result %?", resp) } } @@ -391,7 +386,7 @@ fn test_plaintext_pass_no_pass() { fn test_plaintext_pass_wrong_pass() { let ret = PostgresConnection::try_connect("postgres://pass_user:asdf@localhost"); match ret { - Err(DbError(PostgresDbError { code: ~"28P01", _ })) => (), + Err(DbError(PostgresDbError { code: InvalidPassword, _ })) => (), ret => fail!("Unexpected result %?", ret) } } @@ -414,7 +409,7 @@ fn test_md5_pass_no_pass() { fn test_md5_pass_wrong_pass() { let ret = PostgresConnection::try_connect("postgres://md5_user:asdf@localhost"); match ret { - Err(DbError(PostgresDbError { code: ~"28P01", _ })) => (), + Err(DbError(PostgresDbError { code: InvalidPassword, _ })) => (), ret => fail!("Unexpected result %?", ret) } }