Lightweight 0.20250904.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 SQLINTEGER nativeErrorCode {};
35 std::string sqlState = " "; // 5 characters + null terminator
36 std::string message;
37
38 /// Constructs an ODBC error info object from the given ODBC connection handle.
40 {
41 return FromHandle(SQL_HANDLE_DBC, hDbc);
42 }
43
44 /// Constructs an ODBC error info object from the given ODBC statement handle.
45 static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
46 {
47 return FromHandle(SQL_HANDLE_STMT, hStmt);
48 }
49
50 /// Asserts that the given result is a success code, otherwise throws an exception.
51 static void RequireStatementSuccess(SQLRETURN result, SQLHSTMT hStmt, std::string_view message);
52
53 private:
54 static SqlErrorInfo FromHandle(SQLSMALLINT handleType, SQLHANDLE handle)
55 {
56 SqlErrorInfo info {};
57 info.message = std::string(1024, '\0');
58
59 SQLSMALLINT msgLen {};
60 SQLGetDiagRecA(handleType,
61 handle,
62 1,
63 (SQLCHAR*) info.sqlState.data(),
64 &info.nativeErrorCode,
65 (SQLCHAR*) info.message.data(),
66 (SQLSMALLINT) info.message.capacity(),
67 &msgLen);
68 info.message.resize(msgLen);
69 return info;
70 }
71};
72
73class SqlException: public std::runtime_error
74{
75 public:
76 LIGHTWEIGHT_API explicit SqlException(SqlErrorInfo info,
77 std::source_location location = std::source_location::current());
78
79 // NOLINTNEXTLINE(readability-identifier-naming)
80 [[nodiscard]] LIGHTWEIGHT_FORCE_INLINE SqlErrorInfo const& info() const noexcept
81 {
82 return _info;
83 }
84
85 private:
86 SqlErrorInfo _info;
87};
88
89enum class SqlError : std::int16_t
90{
91 SUCCESS = SQL_SUCCESS,
92 SUCCESS_WITH_INFO = SQL_SUCCESS_WITH_INFO,
93 NODATA = SQL_NO_DATA,
94 FAILURE = SQL_ERROR,
95 INVALID_HANDLE = SQL_INVALID_HANDLE,
96 STILL_EXECUTING = SQL_STILL_EXECUTING,
97 NEED_DATA = SQL_NEED_DATA,
98 PARAM_DATA_AVAILABLE = SQL_PARAM_DATA_AVAILABLE,
99 NO_DATA_FOUND = SQL_NO_DATA_FOUND,
100 UNSUPPORTED_TYPE = 1'000,
101 INVALID_ARGUMENT = 1'001,
102 TRANSACTION_ERROR = 1'002,
103};
104
105struct SqlErrorCategory: std::error_category
106{
107 // NOLINTNEXTLINE(readability-identifier-naming)
108 static SqlErrorCategory const& get() noexcept
109 {
110 static SqlErrorCategory const category;
111 return category;
112 }
113
114 [[nodiscard]] char const* name() const noexcept override
115 {
116 return "Lightweight";
117 }
118
119 [[nodiscard]] std::string message(int code) const override
120 {
121 using namespace std::string_literals;
122 switch (static_cast<SqlError>(code))
123 {
124 case SqlError::SUCCESS:
125 return "SQL_SUCCESS"s;
126 case SqlError::SUCCESS_WITH_INFO:
127 return "SQL_SUCCESS_WITH_INFO"s;
128 case SqlError::NODATA:
129 return "SQL_NO_DATA"s;
130 case SqlError::FAILURE:
131 return "SQL_ERROR"s;
132 case SqlError::INVALID_HANDLE:
133 return "SQL_INVALID_HANDLE"s;
134 case SqlError::STILL_EXECUTING:
135 return "SQL_STILL_EXECUTING"s;
136 case SqlError::NEED_DATA:
137 return "SQL_NEED_DATA"s;
138 case SqlError::PARAM_DATA_AVAILABLE:
139 return "SQL_PARAM_DATA_AVAILABLE"s;
140 case SqlError::UNSUPPORTED_TYPE:
141 return "SQL_UNSUPPORTED_TYPE"s;
142 case SqlError::INVALID_ARGUMENT:
143 return "SQL_INVALID_ARGUMENT"s;
144 case SqlError::TRANSACTION_ERROR:
145 return "SQL_TRANSACTION_ERROR"s;
146 }
147 return std::format("SQL error code {}", code);
148 }
149};
150
151} // namespace Lightweight
152
153// Register our enum as an error code so we can constructor error_code from it
154template <>
155struct std::is_error_code_enum<Lightweight::SqlError>: public std::true_type
156{
157};
158
159/// Tells the compiler that MyErr pairs with MyCategory
160// NOLINTNEXTLINE(readability-identifier-naming)
161inline std::error_code make_error_code(Lightweight::SqlError e)
162{
163 return { static_cast<int>(e), Lightweight::SqlErrorCategory::get() };
164}
165
166template <>
167struct std::formatter<Lightweight::SqlError>: formatter<std::string>
168{
169 auto format(Lightweight::SqlError value, format_context& ctx) const -> format_context::iterator
170 {
171 return formatter<std::string>::format(
172 std::format("{}", Lightweight::SqlErrorCategory().message(static_cast<int>(value))), ctx);
173 }
174};
175
176template <>
177struct std::formatter<Lightweight::SqlErrorInfo>: formatter<std::string>
178{
179 auto format(Lightweight::SqlErrorInfo const& info, format_context& ctx) const -> format_context::iterator
180 {
181 return formatter<std::string>::format(std::format("{} ({}) - {}", info.sqlState, info.nativeErrorCode, info.message),
182 ctx);
183 }
184};
Represents an ODBC SQL error.
Definition SqlError.hpp:33
static void RequireStatementSuccess(SQLRETURN result, SQLHSTMT hStmt, std::string_view message)
Asserts that the given result is a success code, otherwise throws an exception.
static SqlErrorInfo FromStatementHandle(SQLHSTMT hStmt)
Constructs an ODBC error info object from the given ODBC statement handle.
Definition SqlError.hpp:45
static SqlErrorInfo FromConnectionHandle(SQLHDBC hDbc)
Constructs an ODBC error info object from the given ODBC connection handle.
Definition SqlError.hpp:39