Lightweight 0.20250904.0
Loading...
Searching...
No Matches
SqlTransaction.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 "SqlConnection.hpp"
10#include "SqlError.hpp"
11
12#include <format>
13#include <source_location>
14#include <stdexcept>
15
16#include <sql.h>
17#include <sqlext.h>
18#include <sqlspi.h>
19#include <sqltypes.h>
20
21namespace Lightweight
22{
23
24/// Represents the isolation level of a SQL transaction.
25///
26/// The isolation level determines the degree of isolation of data between concurrent transactions.
27/// The higher the isolation level, the less likely it is that two transactions will interfere with each other.
28///
29/// @see https://learn.microsoft.com/en-us/sql/odbc/reference/develop-app/transaction-isolation-levels
30enum class SqlIsolationMode : std::uint8_t
31{
32 /// The default isolation level of the SQL driver or DBMS
33 DriverDefault = 0,
34
35 /// Transactions are not isolated from each other, allowing to concurrently read uncommitted changes.
36 ReadUncommitted = SQL_TXN_READ_UNCOMMITTED,
37
38 /// The transaction waits until the locked writes are committed by other transactions.
39 ///
40 /// The transaction holds a read lock (if it only reads the row) or write lock (if it updates
41 /// or deletes the row) on the current row to prevent other transactions from updating or deleting it.
42 ReadCommitted = SQL_TXN_READ_COMMITTED,
43
44 /// The transaction waits until the locked writes are committed by other transactions.
45 ///
46 /// The transaction holds read locks on all rows it returns to the application and write locks
47 /// on all rows it inserts, updates, or deletes.
48 RepeatableRead = SQL_TXN_REPEATABLE_READ,
49
50 /// The transaction waits until the locked writes are committed by other transactions.
51 ///
52 /// The transaction holds a read lock (if it only reads rows) or write lock (if it can update
53 /// or delete rows) on the range of rows it affects.
54 Serializable = SQL_TXN_SERIALIZABLE,
55};
56
57/// Represents the mode of a SQL transaction to be applied, if not done so explicitly.
58enum class SqlTransactionMode : std::uint8_t
59{
60 NONE,
61 COMMIT,
62 ROLLBACK,
63};
64
65/// Represents an exception that occurred during a SQL transaction.
66///
67/// @see SqlTransaction::Commit(), SqlTransaction::Rollback()
68class SqlTransactionException: public std::runtime_error
69{
70 public:
71 explicit SqlTransactionException(std::string const& message) noexcept:
72 std::runtime_error(message)
73 {
74 }
75};
76
77/// Represents a transaction to a SQL database.
78///
79/// This class is used to control the transaction manually. It disables the auto-commit mode when constructed,
80/// and automatically commits the transaction when destructed if not done so.
81///
82/// This class is designed with RAII in mind, so that the transaction is automatically committed or rolled back
83/// when the object goes out of scope.
84///
85/// @code
86///
87/// void doSomeWork(SqlTransaction& transaction)
88/// {
89/// auto stmt = SqlStatement { transaction.Connection() };
90/// stmt.ExecuteDirect("INSERT INTO table (column) VALUES (42)"); // Do some work
91/// }
92///
93/// void main()
94/// {
95/// auto connection = SqlConnection {};
96/// auto transaction = SqlTransaction { connection };
97///
98/// doSomeWork(transaction);
99///
100/// transaction.Commit();
101/// }
102/// @endcode
104{
105 public:
106 SqlTransaction() = delete;
107 SqlTransaction(SqlTransaction const&) = delete;
108 SqlTransaction& operator=(SqlTransaction const&) = delete;
109
110 SqlTransaction(SqlTransaction&&) = default;
111 SqlTransaction& operator=(SqlTransaction&&) = default;
112
113 /// Construct a new SqlTransaction object, and disable the auto-commit mode, so that the transaction can be
114 /// controlled manually.
115 LIGHTWEIGHT_API explicit SqlTransaction(SqlConnection& connection,
116 SqlTransactionMode defaultMode = SqlTransactionMode::COMMIT,
117 SqlIsolationMode isolationMode = SqlIsolationMode::DriverDefault,
118 std::source_location location = std::source_location::current());
119
120 /// Automatically commit the transaction if not done so
121 LIGHTWEIGHT_API ~SqlTransaction() noexcept;
122
123 /// Get the connection object associated with this transaction.
124 SqlConnection& Connection() noexcept;
125
126 /// Rollback the transaction. Throws an exception if the transaction cannot be rolled back.
127 LIGHTWEIGHT_API void Rollback();
128
129 /// Try to rollback the transaction, and return true if successful, falls otherwise
130 LIGHTWEIGHT_API bool TryRollback() noexcept;
131
132 /// Commit the transaction. Throws an exception if the transaction cannot be committed.
133 LIGHTWEIGHT_API void Commit();
134
135 /// Try to commit the transaction, and return true if successful, falls otherwise
136 LIGHTWEIGHT_API bool TryCommit() noexcept;
137
138 private:
139 /// Retrieves the native handle.
140 [[nodiscard]] SQLHDBC NativeHandle() const noexcept
141 {
142 return m_connection->NativeHandle();
143 }
144
145 private:
146 SqlConnection* m_connection {};
147 SqlTransactionMode m_defaultMode {};
148 std::source_location m_location {};
149};
150
152{
153 return *m_connection;
154}
155
156} // namespace Lightweight
157
158template <>
159struct std::formatter<Lightweight::SqlTransactionMode>: std::formatter<std::string_view>
160{
161 using SqlTransactionMode = Lightweight::SqlTransactionMode;
162 auto format(SqlTransactionMode value, format_context& ctx) const -> format_context::iterator
163 {
164 using namespace std::string_view_literals;
165 string_view name;
166 switch (value)
167 {
168 case SqlTransactionMode::COMMIT:
169 name = "Commit";
170 break;
171 case SqlTransactionMode::ROLLBACK:
172 name = "Rollback";
173 break;
174 case SqlTransactionMode::NONE:
175 name = "None";
176 break;
177 }
178 return std::formatter<string_view>::format(name, ctx);
179 }
180};
181
182template <>
183struct std::formatter<Lightweight::SqlIsolationMode>: std::formatter<std::string_view>
184{
185 using SqlIsolationMode = Lightweight::SqlIsolationMode;
186 auto format(SqlIsolationMode value, format_context& ctx) const -> format_context::iterator
187 {
188 using namespace std::string_view_literals;
189 string_view name;
190 switch (value)
191 {
192 case SqlIsolationMode::DriverDefault:
193 name = "DriverDefault";
194 break;
195 case SqlIsolationMode::ReadUncommitted:
196 name = "ReadUncommitted";
197 break;
198 case SqlIsolationMode::ReadCommitted:
199 name = "ReadCommitted";
200 break;
201 case SqlIsolationMode::RepeatableRead:
202 name = "RepeatableRead";
203 break;
204 case SqlIsolationMode::Serializable:
205 name = "Serializable";
206 break;
207 }
208 return std::formatter<string_view>::format(name, ctx);
209 }
210};
Represents a connection to a SQL database.
SQLHDBC NativeHandle() const noexcept
Retrieves the native handle.
LIGHTWEIGHT_API ~SqlTransaction() noexcept
Automatically commit the transaction if not done so.
LIGHTWEIGHT_API bool TryCommit() noexcept
Try to commit the transaction, and return true if successful, falls otherwise.
LIGHTWEIGHT_API SqlTransaction(SqlConnection &connection, SqlTransactionMode defaultMode=SqlTransactionMode::COMMIT, SqlIsolationMode isolationMode=SqlIsolationMode::DriverDefault, std::source_location location=std::source_location::current())
SqlConnection & Connection() noexcept
Get the connection object associated with this transaction.
LIGHTWEIGHT_API void Rollback()
Rollback the transaction. Throws an exception if the transaction cannot be rolled back.
LIGHTWEIGHT_API void Commit()
Commit the transaction. Throws an exception if the transaction cannot be committed.
LIGHTWEIGHT_API bool TryRollback() noexcept
Try to rollback the transaction, and return true if successful, falls otherwise.