Lightweight 0.20260921.0
Loading...
Searching...
No Matches
SqlRetryClassifier.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Api.hpp"
6#include "SqlError.hpp"
7
8#include <cstdint>
9
10namespace Lightweight
11{
12
13/// @defgroup Retry Retry and Backoff
14/// @brief A reusable, injectable retry/backoff policy for transient database failures.
15///
16/// The pieces are deliberately split so each one is usable — and testable — on its own:
17///
18/// - @ref SqlRetryClassifier decides whether a given @ref SqlErrorInfo is worth another attempt.
19/// It is a per-DBMS extension point, reached through @c SqlQueryFormatter::RetryOps().
20/// - @ref SqlRetrySettings carries the backoff knobs (budget, initial delay, multiplier, caps).
21/// - @ref SqlRetryPolicy combines the two into a decision function and a small driver that runs a
22/// callable until it succeeds or the policy gives up.
23
24/// @ingroup Retry
25/// Whether a failed operation is worth attempting again.
26enum class SqlErrorTransience : std::uint8_t
27{
28 /// The condition is permanent — a constraint violation, a syntax error, a missing table.
29 /// Retrying it will fail identically, so the caller should surface the failure.
31
32 /// The condition is temporary — a dropped connection, a lock timeout, a deadlock-victim
33 /// rollback. The very same statement may well succeed on a later attempt.
35};
36
37/// @ingroup Retry
38/// @brief Dialect-specific classification of SQL errors into transient and permanent.
39///
40/// Every @c SqlQueryFormatter returns a process-singleton instance of the appropriate concrete
41/// classifier via @c SqlQueryFormatter::RetryOps(), which is what keeps per-DBMS error-code
42/// knowledge out of business logic: callers ask the formatter, never @c SqlServerType directly.
43///
44/// The dialects genuinely disagree. PostgreSQL reports a serialization failure as SQLSTATE
45/// @c 40001 and a lock it could not take as @c 55P03; SQL Server reports the deadlock victim as
46/// native error @c 1205 under a generic SQLSTATE; SQLite reports a busy database as a message
47/// string with no usable SQLSTATE at all. A single hard-coded predicate cannot be right for all
48/// three, which is why this is a dispatch point rather than a free function.
49///
50/// Callers normally reach a classifier through @ref SqlRetryPolicy rather than using it directly;
51/// this type is the extension point for adding a new dialect.
52class [[nodiscard]] LIGHTWEIGHT_API SqlRetryClassifier
53{
54 public:
55 SqlRetryClassifier() = default;
56 /// Polymorphic destructor.
57 virtual ~SqlRetryClassifier() = default;
58
60 SqlRetryClassifier& operator=(SqlRetryClassifier const&) = delete;
62 SqlRetryClassifier& operator=(SqlRetryClassifier&&) = delete;
63
64 /// Classifies the given error.
65 ///
66 /// Implementations must be pure: no I/O, no handle access, no hidden state. That is what lets
67 /// a test drive every branch by constructing a @ref SqlErrorInfo, rather than having to
68 /// provoke a real driver failure.
69 ///
70 /// @param error The error reported by the failed attempt.
71 /// @return Whether another attempt could plausibly succeed.
72 [[nodiscard]] virtual SqlErrorTransience Classify(SqlErrorInfo const& error) const noexcept = 0;
73
74 /// Convenience predicate over @ref Classify.
75 ///
76 /// @param error The error reported by the failed attempt.
77 /// @return @c true if @p error is classified as @ref SqlErrorTransience::Transient.
78 [[nodiscard]] bool IsTransient(SqlErrorInfo const& error) const noexcept
79 {
80 return Classify(error) == SqlErrorTransience::Transient;
81 }
82};
83
84/// @ingroup Retry
85/// @brief Returns the dialect-agnostic classifier.
86///
87/// Recognises the transient SQLSTATE classes every ODBC driver shares (connection class @c 08,
88/// transaction-rollback class @c 40, and the @c HYT00 / @c HYT01 timeouts) plus the widely
89/// observed native codes and driver messages. It is the fallback used when the dialect is not
90/// known — for instance when no connection is at hand, or for a server type that has no
91/// formatter of its own.
92[[nodiscard]] LIGHTWEIGHT_API SqlRetryClassifier const& GenericRetryOps() noexcept;
93
94/// @ingroup Retry
95/// @brief Returns the SQLite-specific singleton classifier.
96///
97/// Defined in `SqlRetryPolicy.cpp` for the same reason the advisory-lock handlers are defined out
98/// of line — the formatter overrides delegate to these free functions inline, which keeps every
99/// formatter's vtable weak. See @c SqliteAdvisoryLockOps().
100[[nodiscard]] LIGHTWEIGHT_API SqlRetryClassifier const& SqliteRetryOps() noexcept;
101
102/// @ingroup Retry
103/// @brief Returns the SQL Server-specific singleton classifier. See @ref SqliteRetryOps().
104[[nodiscard]] LIGHTWEIGHT_API SqlRetryClassifier const& SqlServerRetryOps() noexcept;
105
106/// @ingroup Retry
107/// @brief Returns the PostgreSQL-specific singleton classifier. See @ref SqliteRetryOps().
108[[nodiscard]] LIGHTWEIGHT_API SqlRetryClassifier const& PostgreSqlRetryOps() noexcept;
109
110} // namespace Lightweight
Dialect-specific classification of SQL errors into transient and permanent.
bool IsTransient(SqlErrorInfo const &error) const noexcept
virtual ~SqlRetryClassifier()=default
Polymorphic destructor.
virtual SqlErrorTransience Classify(SqlErrorInfo const &error) const noexcept=0
LIGHTWEIGHT_API SqlRetryClassifier const & SqliteRetryOps() noexcept
Returns the SQLite-specific singleton classifier.
LIGHTWEIGHT_API SqlRetryClassifier const & PostgreSqlRetryOps() noexcept
Returns the PostgreSQL-specific singleton classifier. See SqliteRetryOps().
LIGHTWEIGHT_API SqlRetryClassifier const & GenericRetryOps() noexcept
Returns the dialect-agnostic classifier.
LIGHTWEIGHT_API SqlRetryClassifier const & SqlServerRetryOps() noexcept
Returns the SQL Server-specific singleton classifier. See SqliteRetryOps().
Represents an ODBC SQL error.
Definition SqlError.hpp:32