Lightweight 0.20260625.0
Loading...
Searching...
No Matches
SqlError.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#if defined(_WIN32) || defined(_WIN64)
6 #include <Windows.h>
7#endif
8
9#include "Api.hpp"
10
11#include <cstdint>
12#include <format>
13#include <source_location>
14#include <stdexcept>
15#include <system_error>
16
17#include <sql.h>
18#include <sqlext.h>
19#include <sqlspi.h>
20#include <sqltypes.h>
21
22namespace Lightweight
23{
24
25/// @brief Represents an ODBC SQL error.
26///
27/// NOTE: This is a simple wrapper around the SQL return codes. It is not meant to be
28/// comprehensive, but rather to provide a simple way to convert SQL return codes to
29/// std::error_code.
30///
31/// The code below is DRAFT and may be subject to change.
33{
34 /// The native ODBC error code.
35 SQLINTEGER nativeErrorCode {};
36 /// The SQLSTATE diagnostic code (5 characters).
37 std::string sqlState = " "; // 5 characters + null terminator
38 /// The human-readable error message.
39 std::string message;
40
41 /// Constructs an ODBC error info object from the given ODBC connection handle.
43 {
44 return FromHandle(SQL_HANDLE_DBC, hDbc);
45 }
46
47 /// Constructs an ODBC error info object from the given ODBC statement handle.
48 static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
49 {
50 return FromHandle(SQL_HANDLE_STMT, hStmt);
51 }
52
53 /// Constructs an ODBC error info object from the given ODBC environment handle.
55 {
56 return FromHandle(SQL_HANDLE_ENV, hEnv);
57 }
58
59 /// Asserts that the given result is a success code, otherwise throws an exception.
60 static void RequireStatementSuccess(SQLRETURN result, SQLHSTMT hStmt, std::string_view message);
61
62 private:
63 LIGHTWEIGHT_API static SqlErrorInfo FromHandle(SQLSMALLINT handleType, SQLHANDLE handle);
64};
65
66/// @brief Supplies diagnostics for an ODBC handle.
67///
68/// Exists so error paths can be driven from a unit test without a database. Production code never
69/// installs one: with no source configured, @ref SqlErrorInfo reads diagnostics from the driver as
70/// usual. A test installs a source that returns a scripted @ref SqlErrorInfo, which makes the
71/// classification and propagation logic reachable without provoking a real driver failure.
72///
73/// @see SetDiagnosticSource
75{
76 public:
77 SqlDiagnosticSource() = default;
79 SqlDiagnosticSource& operator=(SqlDiagnosticSource const&) = delete;
81 SqlDiagnosticSource& operator=(SqlDiagnosticSource&&) = delete;
82 virtual ~SqlDiagnosticSource() = default;
83
84 /// @brief Returns the diagnostics for the given handle.
85 ///
86 /// @param handleType One of @c SQL_HANDLE_ENV, @c SQL_HANDLE_DBC or @c SQL_HANDLE_STMT.
87 /// @param handle The handle the diagnostics are requested for. A fake may ignore it.
88 /// @return The diagnostics to report for @p handle.
89 [[nodiscard]] virtual SqlErrorInfo Diagnose(SQLSMALLINT handleType, SQLHANDLE handle) = 0;
90};
91
92/// @brief Overrides the source of ODBC diagnostics process-wide.
93///
94/// Intended for tests. Ownership is not transferred and remains with the caller, which must keep
95/// @p source alive until it is cleared. Pass @c nullptr to restore the real ODBC reader.
96///
97/// This mirrors how @c SqlLogger::SetLogger installs a logger, and costs nothing on the success
98/// path: the override is consulted only once a call has already failed and diagnostics are being
99/// retrieved.
100///
101/// @param source The source to install, or @c nullptr to restore the default.
102LIGHTWEIGHT_API void SetDiagnosticSource(SqlDiagnosticSource* source);
103
104/// @brief Returns the currently installed diagnostic source, or @c nullptr if none is installed.
105[[nodiscard]] LIGHTWEIGHT_API SqlDiagnosticSource* GetDiagnosticSource() noexcept;
106
107class SqlException: public std::runtime_error
108{
109 public:
110 LIGHTWEIGHT_API explicit SqlException(SqlErrorInfo info,
111 std::source_location location = std::source_location::current());
112
113 // NOLINTNEXTLINE(readability-identifier-naming)
114 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlErrorInfo const& info() const noexcept
115 {
116 return _info;
117 }
118
119 private:
120 SqlErrorInfo _info;
121};
122
123enum class SqlError : std::int16_t
124{
125 SUCCESS = SQL_SUCCESS,
126 SUCCESS_WITH_INFO = SQL_SUCCESS_WITH_INFO,
127 NODATA = SQL_NO_DATA,
128 FAILURE = SQL_ERROR,
129 INVALID_HANDLE = SQL_INVALID_HANDLE,
130 STILL_EXECUTING = SQL_STILL_EXECUTING,
131 NEED_DATA = SQL_NEED_DATA,
132 PARAM_DATA_AVAILABLE = SQL_PARAM_DATA_AVAILABLE,
133 NO_DATA_FOUND = SQL_NO_DATA_FOUND,
134 UNSUPPORTED_TYPE = 1'000,
135 INVALID_ARGUMENT = 1'001,
136 TRANSACTION_ERROR = 1'002,
137};
138
139struct SqlErrorCategory: std::error_category
140{
141 // NOLINTNEXTLINE(readability-identifier-naming)
142 static SqlErrorCategory const& get() noexcept
143 {
144 static SqlErrorCategory const category;
145 return category;
146 }
147
148 [[nodiscard]] char const* name() const noexcept override
149 {
150 return "Lightweight";
151 }
152
153 [[nodiscard]] std::string message(int code) const override
154 {
155 using namespace std::string_literals;
156 switch (static_cast<SqlError>(code))
157 {
158 case SqlError::SUCCESS:
159 return "SQL_SUCCESS"s;
160 case SqlError::SUCCESS_WITH_INFO:
161 return "SQL_SUCCESS_WITH_INFO"s;
162 case SqlError::NODATA:
163 return "SQL_NO_DATA"s;
164 case SqlError::FAILURE:
165 return "SQL_ERROR"s;
166 case SqlError::INVALID_HANDLE:
167 return "SQL_INVALID_HANDLE"s;
168 case SqlError::STILL_EXECUTING:
169 return "SQL_STILL_EXECUTING"s;
170 case SqlError::NEED_DATA:
171 return "SQL_NEED_DATA"s;
172 case SqlError::PARAM_DATA_AVAILABLE:
173 return "SQL_PARAM_DATA_AVAILABLE"s;
174 case SqlError::UNSUPPORTED_TYPE:
175 return "SQL_UNSUPPORTED_TYPE"s;
176 case SqlError::INVALID_ARGUMENT:
177 return "SQL_INVALID_ARGUMENT"s;
178 case SqlError::TRANSACTION_ERROR:
179 return "SQL_TRANSACTION_ERROR"s;
180 }
181 return std::format("SQL error code {}", code);
182 }
183};
184
185} // namespace Lightweight
186
187// Register our enum as an error code so we can constructor error_code from it
188template <>
189struct std::is_error_code_enum<Lightweight::SqlError>: public std::true_type
190{
191};
192
193/// Tells the compiler that MyErr pairs with MyCategory
194// NOLINTNEXTLINE(readability-identifier-naming)
195inline std::error_code make_error_code(Lightweight::SqlError e)
196{
197 return { static_cast<int>(e), Lightweight::SqlErrorCategory::get() };
198}
199
200template <>
201struct std::formatter<Lightweight::SqlError>: formatter<std::string>
202{
203 auto format(Lightweight::SqlError value, format_context& ctx) const -> format_context::iterator
204 {
205 // Use the shared singleton instead of default-constructing a fresh category for every
206 // format call — the singleton is the same instance returned by `make_error_code()`.
207 return formatter<std::string>::format(Lightweight::SqlErrorCategory::get().message(static_cast<int>(value)), ctx);
208 }
209};
210
211template <>
212struct std::formatter<Lightweight::SqlErrorInfo>: formatter<std::string>
213{
214 auto format(Lightweight::SqlErrorInfo const& info, format_context& ctx) const -> format_context::iterator
215 {
216 return formatter<std::string>::format(std::format("{} ({}) - {}", info.sqlState, info.nativeErrorCode, info.message),
217 ctx);
218 }
219};
Supplies diagnostics for an ODBC handle.
Definition SqlError.hpp:75
virtual SqlErrorInfo Diagnose(SQLSMALLINT handleType, SQLHANDLE handle)=0
Returns the diagnostics for the given handle.
Represents an ODBC SQL error.
Definition SqlError.hpp:33
SQLINTEGER nativeErrorCode
The native ODBC error code.
Definition SqlError.hpp:35
std::string message
The human-readable error message.
Definition SqlError.hpp:39
static void RequireStatementSuccess(SQLRETURN result, SQLHSTMT hStmt, std::string_view message)
Asserts that the given result is a success code, otherwise throws an exception.
std::string sqlState
The SQLSTATE diagnostic code (5 characters).
Definition SqlError.hpp:37
static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
Constructs an ODBC error info object from the given ODBC statement handle.
Definition SqlError.hpp:48
static SqlErrorInfo FromEnvironmentHandle(SQLHENV hEnv)
Constructs an ODBC error info object from the given ODBC environment handle.
Definition SqlError.hpp:54
static SqlErrorInfo FromConnectionHandle(SQLHDBC hDbc)
Constructs an ODBC error info object from the given ODBC connection handle.
Definition SqlError.hpp:42