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