userver: userver/storages/postgres/exceptions.hpp Source File
⚠️ This is the documentation for an old userver version. Click here to switch to the latest version.
All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages Concepts
exceptions.hpp
Go to the documentation of this file.
1#pragma once
2
3/// @file userver/storages/postgres/exceptions.hpp
4/// @brief Postgres errors
5
6#include <stdexcept>
7#include <string_view>
8
9#include <fmt/format.h>
10
11#include <userver/storages/postgres/dsn.hpp>
12#include <userver/storages/postgres/io/traits.hpp>
13#include <userver/storages/postgres/message.hpp>
14
15#include <userver/compiler/demangle.hpp>
16#include <userver/utils/underlying_value.hpp>
17
18USERVER_NAMESPACE_BEGIN
19
20namespace storages::postgres {
21
22/**
23 * @page pg_errors uPg: Postgres errors
24 *
25 * Base class for all PostgreSQL errors is Error which is derived from
26 * std::runtime_error. This is done to simplify exception handling.
27 *
28 * There are two base types of errors: runtime (RuntimeError) and logic
29 * (LogicError).
30 *
31 * **Logic errors** are a consequence of faulty logic within the program such as
32 * violating logical preconditions or invariants and may be preventable by
33 * correcting the program.
34 *
35 * **Runtime errors** are due to events beyond the scope of the program, such as
36 * network failure, faulty configuration file, unique index violation etc. A
37 * user can catch such an error and recover from it by reconnecting, providing
38 * a decent default for configuration or modifying the key value.
39 *
40 * Both logic and runtime errors can contain a postgres server message
41 * (Message). Those are ServerLogicError and ServerRuntimeError
42 * respectively. These errors occur on the server side and are translated into
43 * exceptions by the driver. Server errors are descendants of either
44 * ServerLogicError or ServerRuntimeError and their hierarchy corresponds to SQL
45 * error classes.
46 *
47 * Some server errors, such as IntegrityConstraintViolation, have a more
48 * detailed hierarchy to distinguish errors in catch clauses.
49 *
50 * Server errors have the following hierarchy:
51 * - ServerLogicError
52 * - SqlStatementNotYetComplete
53 * - FeatureNotSupported
54 * - InvalidRoleSpecification
55 * - CardinalityViolation
56 * - InvalidObjectName
57 * - InvalidAuthorizationSpecification
58 * - SyntaxError
59 * - AccessRuleViolation (TODO Decide if this is actually a runtime error)
60 * - ServerRuntimeError
61 * - TriggeredActionException
62 * - LocatorException
63 * - InvalidGrantor
64 * - DiagnosticsException
65 * - DataException
66 * - DuplicatePreparedStatement
67 * - IntegrityConstraintViolation
68 * - RestrictViolation
69 * - NotNullViolation (TODO Make it a logic error)
70 * - ForeignKeyViolation
71 * - UniqueViolation
72 * - CheckViolation
73 * - ExclusionViolation
74 * - TriggeredDataChangeViolation
75 * - WithCheckOptionViolation
76 * - InvalidCursorState
77 * - InvalidSqlStatementName
78 * - InvalidTransactionState
79 * - DependentPrivilegeDescriptorsStillExist
80 * - InvalidTransactionTermination
81 * - ExternalRoutineException
82 * - ExternalRoutineInvocationException
83 * - SavepointException
84 * - SqlRoutineException
85 * - TransactionRollback
86 * - InsufficientResources
87 * - ProgramLimitExceeded
88 * - ObjectNotInPrerequisiteState
89 * - OperatorIntervention
90 * - QueryCancelled
91 * - AdminShutdown
92 * - CrashShutdown
93 * - CannotConnectNow
94 * - DatabaseDropped
95 * - SystemError
96 * - SnapshotFailure
97 * - ConfigurationFileError
98 * - FdwError
99 * - PlPgSqlError
100 * - InternalServerError
101 *
102 * Besides server errors there are exceptions thrown by the driver itself,
103 * those are:
104 * - LogicError
105 * - ResultSetError
106 * - FieldIndexOutOfBounds
107 * - FieldNameDoesntExist
108 * - FieldTupleMismatch
109 * - FieldValueIsNull
110 * - InvalidBinaryBuffer
111 * - InvalidInputBufferSize
112 * - InvalidParserCategory
113 * - InvalidTupleSizeRequested
114 * - NonSingleColumnResultSet
115 * - NonSingleRowResultSet
116 * - NoBinaryParser
117 * - RowIndexOutOfBounds
118 * - TypeCannotBeNull
119 * - UnknownBufferCategory
120 * - UserTypeError
121 * - CompositeSizeMismatch
122 * - CompositeMemberTypeMismatch
123 * - ArrayError
124 * - DimensionMismatch
125 * - InvalidDimensions
126 * - NumericError
127 * - NumericOverflow
128 * - ValueIsNaN
129 * - InvalidRepresentation
130 * - InvalidInputFormat
131 * - EnumerationError
132 * - InvalidEnumerationLiteral
133 * - InvalidEnumerationValue
134 * - TransactionError
135 * - AlreadyInTransaction
136 * - NotInTransaction
137 * - UnsupportedInterval
138 * - BoundedRangeError
139 * - BitStringError
140 * - BitStringOverflow
141 * - InvalidBitStringRepresentation
142 * - RuntimeError
143 * - ConnectionError
144 * - ClusterUnavailable
145 * - CommandError
146 * - ConnectionFailed
147 * - ServerConnectionError (contains a message from server)
148 * - ConnectionTimeoutError
149 * - ConnectionBusy
150 * - ConnectionInterrupted
151 * - PoolError
152 * - ClusterError
153 * - InvalidConfig
154 * - InvalidDSN
155 *
156 *
157 * ----------
158 *
159 * @htmlonly <div class="bottom-nav"> @endhtmlonly
160 * ⇦ @ref pg_user_row_types | @ref pg_topology ⇨
161 * @htmlonly </div> @endhtmlonly
162 */
163
164//@{
165/** @name Generic driver errors */
166
167/// @brief Base class for all exceptions that may be thrown by the driver.
168class Error : public std::runtime_error {
169 using runtime_error::runtime_error;
170};
171
172/// @brief Base Postgres logic error.
173/// Reports errors that are consequences of erroneous driver usage,
174/// such as invalid query syntax, absence of appropriate parsers, out of range
175/// errors etc.
176/// These can be avoided by fixing code.
177class LogicError : public Error {
178 using Error::Error;
179};
180
181/// @brief Base Postgres runtime error.
182/// Reports errors that are consequences of erroneous data, misconfiguration,
183/// network errors etc.
184class RuntimeError : public Error {
185 using Error::Error;
186};
187
188/// @brief Error that was reported by PosgtreSQL server
189/// Contains the message sent by the server.
190/// Templated class because the errors can be both runtime and logic.
191template <typename Base>
192class ServerError : public Base {
193 public:
194 explicit ServerError(const Message& msg)
195 : Base(msg.GetMessage()), msg_{msg} {}
196
197 const Message& GetServerMessage() const { return msg_; }
198
199 Message::Severity GetSeverity() const { return msg_.GetSeverity(); }
200 SqlState GetSqlState() const { return msg_.GetSqlState(); }
201
202 private:
203 Message msg_;
204};
205
206using ServerLogicError = ServerError<LogicError>;
207using ServerRuntimeError = ServerError<RuntimeError>;
208//@}
209
210//@{
211/** @name Connection errors */
213 using RuntimeError::RuntimeError;
214};
215
216/// @brief Exception is thrown when a single connection fails to connect
218 public:
219 explicit ConnectionFailed(const Dsn& dsn)
220 : ConnectionError(fmt::format("{} Failed to connect to PostgreSQL server",
221 DsnCutPassword(dsn))) {}
222 ConnectionFailed(const Dsn& dsn, std::string_view message)
223 : ConnectionError(fmt::format("{} {}", DsnCutPassword(dsn), message)) {}
224};
225
226/// @brief Connection error reported by PostgreSQL server.
227/// Doc: https://www.postgresql.org/docs/12/static/errcodes-appendix.html
228/// Class 08 - Connection exception
231};
232
233/// @brief Indicates errors during pool operation
234class PoolError : public RuntimeError {
235 public:
236 PoolError(std::string_view msg, std::string_view db_name)
237 : PoolError(fmt::format("{}, db_name={}", msg, db_name)) {}
238
239 PoolError(std::string_view msg)
240 : RuntimeError::RuntimeError(
241 fmt::format("Postgres ConnectionPool error: {}", msg)) {}
242};
243
245 using ConnectionError::ConnectionError;
246};
247
248/// @brief Error when invoking a libpq function
250 using ConnectionError::ConnectionError;
251};
252
253/// @brief A network operation on a connection has timed out
255 using ConnectionError::ConnectionError;
256};
257
259 using RuntimeError::RuntimeError;
260};
261
262/// @brief An attempt to make a query to server was made while there is another
263/// query in flight.
265 using RuntimeError::RuntimeError;
266};
267
268/// @brief A network operation was interrupted by task cancellation.
270 using RuntimeError::RuntimeError;
271};
272
273//@}
274
275//@{
276/** @name SQL errors */
277//@{
278/** @name Class 03 — SQL Statement Not Yet Complete */
279/// A programming error, a statement is sent before other statement's results
280/// are processed.
283};
284//@}
285
286//@{
287/** @name Class 09 — Triggered Action Exception */
290};
291//@}
292
293//@{
294/** @name Class 0A — Feature Not Supported */
297};
298//@}
299
300//@{
301/** @name Class 0F - Locator Exception */
304};
305//@}
306
307//@{
308/** @name Class 0L - Invalid Grantor */
311};
312//@}
313
314//@{
315/** @name Class 0P - Invalid Role Specification */
318};
319//@}
320
321//@{
322/** @name Class 0Z - Diagnostics Exception */
325};
326//@}
327
328//@{
329/** @name Class 21 - Cardinality Violation */
332};
333//@}
334
335//@{
336/** @name Class 22 — Data Exception */
337/// @brief Base class for data exceptions
338/// Doc: https://www.postgresql.org/docs/12/static/errcodes-appendix.html
341};
342//@}
343
344//@{
345/** @name Class 23 — Integrity Constraint Violation */
346// TODO Shortcut accessors to respective message fields
347/// @brief Base class for integrity constraint violation errors.
348/// Doc: https://www.postgresql.org/docs/12/static/errcodes-appendix.html
350 public:
352
353 std::string GetSchema() const { return GetServerMessage().GetSchema(); }
354 std::string GetTable() const { return GetServerMessage().GetTable(); }
355 std::string GetConstraint() const {
356 return GetServerMessage().GetConstraint();
357 }
358};
359
361 using IntegrityConstraintViolation::IntegrityConstraintViolation;
362};
363
365 using IntegrityConstraintViolation::IntegrityConstraintViolation;
366};
367
369 using IntegrityConstraintViolation::IntegrityConstraintViolation;
370};
371
373 using IntegrityConstraintViolation::IntegrityConstraintViolation;
374};
375
377 using IntegrityConstraintViolation::IntegrityConstraintViolation;
378};
379
381 using IntegrityConstraintViolation::IntegrityConstraintViolation;
382};
383
384/// Class 27 - Triggered Data Change Violation
386 using IntegrityConstraintViolation::IntegrityConstraintViolation;
387};
388
389/// Class 44 - WITH CHECK OPTION Violation
391 using IntegrityConstraintViolation::IntegrityConstraintViolation;
392};
393//@}
394
395//@{
396/** @name Class 24 - Invalid Cursor State */
399};
400//@}
401
402//@{
403/** @name Class 25 — Invalid Transaction State */
406};
407//@}
408
409//@{
410/** @name Class 26 - Invalid SQL Statement Name */
411/// This exception is thrown in case a prepared statement doesn't exist
414};
415//@}
416
417//@{
418/** @name Invalid object name, several classes */
419/// @brief Exception class for several Invalid * Name classes.
420/// Class 34 - Invalid Cursor Name
421/// Class 3D - Invalid Catalogue Name
422/// Class 3F - Invalid Schema Name
423/// TODO Add documentation (links) on the error classes
424/// TODO Split exception classes if needed based on documentation
427};
428//@}
429
430//@{
431/** @name Class 28 - Invalid Authorisation Specification */
434};
435//@}
436
437//@{
438/** @name Class 2B - Dependent Privilege Descriptors Still Exist */
441};
442//@}
443
444//@{
445/** @name Class 2D - Invalid Transaction Termination */
448};
449//@}
450
451//@{
452/** @name Class 38 - External Routine Exception */
455};
456//@}
457
458//@{
459/** @name Class 39 - External Routine Invocation Exception */
462};
463//@}
464
465//@{
466/** @name Class 3B - Savepoint Exception */
469};
470//@}
471
472//@{
473/** @name Class 2F — SQL Routine Exception */
476};
477//@}
478
479//@{
480/** @name Class 40 — Transaction Rollback */
483};
484//@}
485
486//@{
487/** @name Class 42 — Syntax Error or Access Rule Violation */
490};
491
494};
495
498};
499
500//@}
501
502//@{
503/** @name Class 53 - Insufficient Resources */
506};
507//@}
508
509//@{
510/** @name Class 54 - Program Limit Exceeded */
513};
514//@}
515
516//@{
517/** @name Class 55 - Object Not In Prerequisite State */
520};
521//@}
522
523//@{
524/** @name Class 57 - Operator Intervention */
527};
528
530 using OperatorIntervention::OperatorIntervention;
531};
532
534 using OperatorIntervention::OperatorIntervention;
535};
536
538 using OperatorIntervention::OperatorIntervention;
539};
540
542 using OperatorIntervention::OperatorIntervention;
543};
544
546 using OperatorIntervention::OperatorIntervention;
547};
548//@}
549
550//@{
551/** @name Class 58 - System Error (errors external to PostgreSQL itself) */
554};
555//@}
556
557//@{
558/** @name Class 72 — Snapshot Failure */
561};
562//@}
563
564//@{
565/** @name Class F0 — Configuration File Error */
568};
569//@}
570
571//@{
572/** @name Class HV — Foreign Data Wrapper Error (SQL/MED) */
575};
576//@}
577
578//@{
579/** @name Class P0 — PL/pgSQL Error */
582};
583//@}
584
585//@{
586/** @name Class XX — Internal Error */
589};
590//@}
591//@}
592
593//@{
594/** @name Transaction errors */
596 using LogicError::LogicError;
597};
598
600 public:
601 AlreadyInTransaction()
602 : TransactionError("Connection is already in a transaction block") {}
603};
604
606 public:
607 NotInTransaction()
608 : TransactionError("Connection is not in a transaction block") {}
609 NotInTransaction(const std::string& msg) : TransactionError(msg) {}
610};
611//@}
612
613//@{
614/** @name Result set usage errors */
615
617 public:
618 ResultSetError(std::string msg)
619 : LogicError::LogicError(msg), msg_(std::move(msg)) {}
620
621 void AddMsgSuffix(std::string_view str) { msg_ += str; }
622
623 const char* what() const noexcept override { return msg_.c_str(); }
624
625 private:
626 std::string msg_;
627};
628
629/// @brief Result set has less rows than the requested row index.
631 public:
632 RowIndexOutOfBounds(std::size_t index)
633 : ResultSetError(fmt::format("Row index {} is out of bounds", index)) {}
634};
635
636/// @brief Result set has less columns that the requested index.
638 public:
639 FieldIndexOutOfBounds(std::size_t index)
640 : ResultSetError(fmt::format("Field index {} is out of bounds", index)) {}
641};
642
643/// @brief Result set doesn't have field with the requested name.
645 public:
646 FieldNameDoesntExist(std::string_view name)
647 : ResultSetError(fmt::format("Field name '{}' doesn't exist", name)) {}
648};
649
650/// @brief Data extraction from a null field value to a non-nullable type
651/// requested.
653 public:
654 template <typename T>
655 FieldValueIsNull(std::size_t field_index, std::string_view field_name,
656 const T&)
657 : ResultSetError(fmt::format("Field #{} name `{}` C++ type `{}` value is "
658 "null, forgot `std::optional`?",
659 field_index, field_name,
660 compiler::GetTypeName<T>())) {}
661};
662
663/// @brief A value of a non-nullable type requested to be set null.
664/// Can occur if io::traits::IsNullable for the type is specialised as
665/// true_type, but io::traits::GetSetNull is not specialized appropriately.
667 public:
668 TypeCannotBeNull(std::string_view type)
669 : ResultSetError(fmt::format("{} cannot be null", type)) {}
670};
671
672/// @brief Field buffer contains different category of data than expected by
673/// data parser.
675 public:
676 InvalidParserCategory(std::string_view type, io::BufferCategory parser,
677 io::BufferCategory buffer)
678 : ResultSetError(fmt::format("Buffer category '{}' doesn't match the "
679 "category of the parser '{}' for type '{}'",
680 ToString(buffer), ToString(parser), type)) {}
681};
682
683/// @brief While checking result set types, failed to determine the buffer
684/// category for a type oid.
685/// The context string is formed by the ResultSet and will have the form
686/// of 'result set field `foo` type `my_schema.bar` field `baz` array element'
688 public:
689 UnknownBufferCategory(std::string_view context, Oid type_oid)
690 : ResultSetError(
691 fmt::format("Query {} doesn't have a parser. Type oid is {}",
692 context, type_oid)),
693 type_oid{type_oid} {};
694
695 const Oid type_oid;
696};
697
698/// @brief A field in a result set doesn't have a binary parser.
700 using ResultSetError::ResultSetError;
701};
702
703/// @brief Buffer size is invalid for a fixed-size type.
704/// Can occur when a wrong field type is requested for reply.
706 public:
707 InvalidInputBufferSize(std::size_t size, std::string_view message)
708 : ResultSetError(
709 fmt::format("Buffer size {} is invalid: {}", size, message)) {}
710};
711
712/// @brief Binary buffer contains invalid data.
713/// Can occur when parsing binary buffers containing multiple fields.
715 public:
716 InvalidBinaryBuffer(const std::string& message)
717 : ResultSetError("Invalid binary buffer: " + message) {}
718};
719
720/// @brief A tuple was requested to be parsed out of a row that doesn't have
721/// enough fields.
723 public:
724 InvalidTupleSizeRequested(std::size_t field_count, std::size_t tuple_size)
725 : ResultSetError("Invalid tuple size requested. Field count " +
726 std::to_string(field_count) + " < tuple size " +
727 std::to_string(tuple_size)) {}
728};
729
730/// @brief A row or result set requested to be treated as a single column, but
731/// contains more than one column.
733 public:
734 NonSingleColumnResultSet(std::size_t actual_size,
735 const std::string& type_name,
736 const std::string& func)
738 "Parsing the row consisting of " + std::to_string(actual_size) +
739 " columns as " + type_name +
740 " is ambiguous as it can be used both for "
741 "single column type and for a row. " +
742 "Please use " + func + "<" + type_name + ">(kRowTag) or " + func +
743 "<" + type_name + ">(kFieldTag) to resolve the ambiguity.") {}
744};
745
746/// @brief A result set containing a single row was expected
748 public:
749 explicit NonSingleRowResultSet(std::size_t actual_size)
750 : ResultSetError(fmt::format("A single row result set was expected. "
751 "Actual result set size is {}",
752 actual_size)) {}
753};
754
755/// @brief A row was requested to be parsed based on field names/indexed,
756/// the count of names/indexes doesn't match the tuple size.
758 public:
759 FieldTupleMismatch(std::size_t field_count, std::size_t tuple_size)
760 : ResultSetError(fmt::format(
761 "Invalid tuple size requested. Field count {} != tuple size {}",
762 field_count, tuple_size)) {}
763};
764
765//@}
766
767//@{
768/// @brief Base error when working with mapped types
769class UserTypeError : public LogicError {
770 using LogicError::LogicError;
771};
772
773/// @brief PostgreSQL composite type has different count of members from
774/// the C++ counterpart.
776 public:
777 CompositeSizeMismatch(std::size_t pg_size, std::size_t cpp_size,
778 std::string_view cpp_type)
779 : UserTypeError(
780 fmt::format("Invalid composite type size. PostgreSQL type has {} "
781 "members, C++ type '{}' has {} members",
782 pg_size, cpp_type, cpp_size)) {}
783};
784
785/// @brief PostgreSQL composite type has different member type that the C++
786/// mapping suggests.
788 public:
789 CompositeMemberTypeMismatch(std::string_view pg_type_schema,
790 std::string_view pg_type_name,
791 std::string_view field_name, Oid pg_oid,
792 Oid user_oid)
793 : UserTypeError(fmt::format(
794 "Type mismatch for {}.{} field {}. In database the type "
795 "oid is {}, user supplied type oid is {}",
796 pg_type_schema, pg_type_name, field_name, pg_oid, user_oid)) {}
797};
798
799//@}
800
801//@{
802/** @name Array errors */
803/// @brief Base error when working with array types.
804class ArrayError : public LogicError {
805 using LogicError::LogicError;
806};
807
808/// @brief Array received from postgres has different dimensions from those of
809/// C++ container.
811 public:
812 DimensionMismatch()
813 : ArrayError("Array dimensions don't match dimensions of C++ type") {}
814};
815
817 public:
818 InvalidDimensions(std::size_t expected, std::size_t actual)
819 : ArrayError(fmt::format("Invalid dimension size {}. Expected {}", actual,
820 expected)) {}
821};
822
823//@}
824
825//@{
826/** @name Numeric/decimal datatype errors */
827class NumericError : public LogicError {
828 using LogicError::LogicError;
829};
830
831/// Value in PostgreSQL binary buffer cannot be represented by a given C++ type
833 using NumericError::NumericError;
834};
835
836/// PostgreSQL binary buffer contains NaN value, but the given C++ type doesn't
837/// support NaN value
838class ValueIsNaN : public NumericError {
839 using NumericError::NumericError;
840};
841
842/// Integral representation for a numeric contains invalid data
844 using NumericError::NumericError;
845};
846//@}
847
848/// @brief Invalid format for input data.
849///
850/// Can occur when a numeric string representation cannot be parsed for sending
851/// in binary buffers
853 using LogicError::LogicError;
854};
855
856//@{
857/** @name Enumeration type errors */
859 using LogicError::LogicError;
860};
861
863 public:
864 InvalidEnumerationLiteral(std::string_view type_name,
865 std::string_view literal)
866 : EnumerationError(
867 fmt::format("Invalid enumeration literal '{}' for enum type '{}'",
868 literal, type_name)) {}
869};
870
872 public:
873 template <typename Enum>
874 explicit InvalidEnumerationValue(Enum val)
875 : EnumerationError(
876 fmt::format("Invalid enumeration value '{}' for enum type '{}'",
877 USERVER_NAMESPACE::utils::UnderlyingValue(val),
878 compiler::GetTypeName<Enum>())) {}
879};
880//@}
881
882/// PostgreSQL interval datatype contains months field, which cannot be
883/// converted to microseconds unambiguously
885 public:
886 UnsupportedInterval()
887 : LogicError("PostgreSQL intervals containing months are not supported") {
888 }
889};
890
891/// PostgreSQL range type has at least one end unbound
893 public:
894 BoundedRangeError(std::string_view message)
895 : LogicError(fmt::format(
896 "PostgreSQL range violates bounded range invariant: {}", message)) {
897 }
898};
899
900//@{
901/** @name bit/bit varying type errors */
902
903/// @brief Base error when working with bit string types.
905 public:
906 using LogicError::LogicError;
907};
908
909/// Value in PostgreSQL binary buffer cannot be represented by a given C++ type
911 public:
912 BitStringOverflow(std::size_t actual, std::size_t expected)
913 : BitStringError(fmt::format("Invalid bit container size {}. Expected {}",
914 actual, expected)) {}
915};
916
917/// Value in PostgreSQL binary buffer cannot be represented as bit string type
919 public:
920 InvalidBitStringRepresentation()
921 : BitStringError("Invalid bit or bit varying type representation") {}
922};
923//@}
924
925//@{
926/** @name Misc exceptions */
927class InvalidDSN : public RuntimeError {
928 public:
929 InvalidDSN(std::string_view dsn, std::string_view err)
930 : RuntimeError(fmt::format("Invalid dsn '{}': {}", dsn, err)) {}
931};
932
934 using RuntimeError::RuntimeError;
935};
936
938 using LogicError::LogicError;
939};
940
941//@}
942
943//@{
944/** @name ip type errors */
946 public:
947 using LogicError::LogicError;
948};
949
951 public:
952 explicit IpAddressInvalidFormat(std::string_view str)
953 : IpAddressError(fmt::format("IP address invalid format. {}", str)) {}
954};
955//@}
956
957} // namespace storages::postgres
958
959USERVER_NAMESPACE_END