userver: userver/storages/postgres/exceptions.hpp Source File
Loading...
Searching...
No Matches
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