Lightweight 0.20260303.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 /// Constructs a transaction exception with the given error message.
72 explicit SqlTransactionException(std::string const& message) noexcept:
73 std::runtime_error(message)
74 {
75 }
76};
77
78/// Represents a transaction to a SQL database.
79///
80/// This class is used to control the transaction manually. It disables the auto-commit mode when constructed,
81/// and automatically commits the transaction when destructed if not done so.
82///
83/// This class is designed with RAII in mind, so that the transaction is automatically committed or rolled back
84/// when the object goes out of scope.
85///
86/// @code
87///
88/// void doSomeWork(SqlTransaction& transaction)
89/// {
90/// auto stmt = SqlStatement { transaction.Connection() };
91/// stmt.ExecuteDirect("INSERT INTO table (column) VALUES (42)"); // Do some work
92/// }
93///
94/// void main()
95/// {
96/// auto connection = SqlConnection {};
97/// auto transaction = SqlTransaction { connection };
98///
99/// doSomeWork(transaction);
100///
101/// transaction.Commit();
102/// }
103/// @endcode
105{
106 public:
107 SqlTransaction() = delete;
108 SqlTransaction(SqlTransaction const&) = delete;
109 SqlTransaction& operator=(SqlTransaction const&) = delete;
110
111 /// Default move constructor.
113 /// Default move assignment operator.
115
116 /// Construct a new SqlTransaction object, and disable the auto-commit mode, so that the transaction can be
117 /// controlled manually.
118 LIGHTWEIGHT_API explicit SqlTransaction(SqlConnection& connection,
119 SqlTransactionMode defaultMode = SqlTransactionMode::COMMIT,
120 SqlIsolationMode isolationMode = SqlIsolationMode::DriverDefault,
121 std::source_location location = std::source_location::current());
122
123 /// Automatically commit the transaction if not done so
124 LIGHTWEIGHT_API ~SqlTransaction() noexcept;
125
126 /// Get the connection object associated with this transaction.
127 SqlConnection& Connection() noexcept;
128
129 /// Rollback the transaction. Throws an exception if the transaction cannot be rolled back.
130 LIGHTWEIGHT_API void Rollback();
131
132 /// Try to rollback the transaction, and return true if successful, falls otherwise
133 LIGHTWEIGHT_API bool TryRollback() noexcept;
134
135 /// Commit the transaction. Throws an exception if the transaction cannot be committed.
136 LIGHTWEIGHT_API void Commit();
137
138 /// Try to commit the transaction, and return true if successful, falls otherwise
139 LIGHTWEIGHT_API bool TryCommit() noexcept;
140
141 private:
142 /// Retrieves the native handle.
143 [[nodiscard]] SQLHDBC NativeHandle() const noexcept
144 {
145 return m_connection->NativeHandle();
146 }
147
148 private:
149 SqlConnection* m_connection {};
150 SqlTransactionMode m_defaultMode {};
151 std::source_location m_location {};
152};
153
155{
156 return *m_connection;
157}
158
159} // namespace Lightweight
160
161template <>
162struct std::formatter<Lightweight::SqlTransactionMode>: std::formatter<std::string_view>
163{
164 using SqlTransactionMode = Lightweight::SqlTransactionMode;
165 auto format(SqlTransactionMode value, format_context& ctx) const -> format_context::iterator
166 {
167 using namespace std::string_view_literals;
168 string_view name;
169 switch (value)
170 {
171 case SqlTransactionMode::COMMIT:
172 name = "Commit";
173 break;
174 case SqlTransactionMode::ROLLBACK:
175 name = "Rollback";
176 break;
177 case SqlTransactionMode::NONE:
178 name = "None";
179 break;
180 }
181 return std::formatter<string_view>::format(name, ctx);
182 }
183};
184
185template <>
186struct std::formatter<Lightweight::SqlIsolationMode>: std::formatter<std::string_view>
187{
188 using SqlIsolationMode = Lightweight::SqlIsolationMode;
189 auto format(SqlIsolationMode value, format_context& ctx) const -> format_context::iterator
190 {
191 using namespace std::string_view_literals;
192 string_view name;
193 switch (value)
194 {
195 case SqlIsolationMode::DriverDefault:
196 name = "DriverDefault";
197 break;
198 case SqlIsolationMode::ReadUncommitted:
199 name = "ReadUncommitted";
200 break;
201 case SqlIsolationMode::ReadCommitted:
202 name = "ReadCommitted";
203 break;
204 case SqlIsolationMode::RepeatableRead:
205 name = "RepeatableRead";
206 break;
207 case SqlIsolationMode::Serializable:
208 name = "Serializable";
209 break;
210 }
211 return std::formatter<string_view>::format(name, ctx);
212 }
213};
Represents a connection to a SQL database.
SQLHDBC NativeHandle() const noexcept
Retrieves the native handle.
SqlTransactionException(std::string const &message) noexcept
Constructs a transaction exception with the given error message.
SqlTransaction(SqlTransaction &&)=default
Default move constructor.
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.
SqlTransaction & operator=(SqlTransaction &&)=default
Default move assignment operator.