Lightweight 0.20260921.0
Loading...
Searching...
No Matches
SqlScopedLock.hpp
1// SPDX-License-Identifier: Apache-2.0
2
3#pragma once
4
5#include "Api.hpp"
6#include "SqlAdvisoryLock.hpp"
7#include "SqlConnection.hpp"
8
9#include <chrono>
10#include <expected>
11#include <string>
12#include <string_view>
13
14namespace Lightweight
15{
16
17/// @ingroup CoreApi
18/// RAII-style cross-process advisory lock.
19///
20/// Provides a distributed locking mechanism so that only one process at a
21/// time can hold a named token. The dialect-specific primitive is selected
22/// by the active `SqlQueryFormatter`'s `AdvisoryLockOps()` handler:
23/// - SQL Server: `sp_getapplock` / `sp_releaseapplock`
24/// - PostgreSQL: `pg_advisory_lock` / `pg_advisory_unlock`
25/// - SQLite: `_lightweight_locks` table guarded by a unique constraint
26///
27/// `SqlScopedLock` itself contains zero per-DBMS branching — adding a new
28/// dialect only requires implementing a `SqlAdvisoryLockHandler` and wiring
29/// it to the new formatter's `AdvisoryLockOps()`.
30///
31/// @code
32/// // Throws on failure (timeout, deadlock, or driver error):
33/// auto lock = SqlScopedLock { connection, "my-resource" };
34///
35/// // Or, with structured error handling:
36/// auto maybeLock = SqlScopedLock::TryConstruct(connection, "my-resource");
37/// if (!maybeLock)
38/// std::println(stderr, "{}", maybeLock.error().message);
39/// @endcode
41{
42 public:
43 /// Acquire a named advisory lock.
44 ///
45 /// @param connection Database connection to use for locking.
46 /// @param lockName Name of the lock (cooperative; any string).
47 /// @param timeout Maximum time to wait for lock acquisition.
48 /// @throws std::runtime_error if the lock cannot be acquired (timeout, deadlock,
49 /// or driver error). The thrown exception's `what()` contains a
50 /// human-readable explanation. For programmatic access to the failure
51 /// reason, use the non-throwing `TryConstruct` factory.
52 LIGHTWEIGHT_API explicit SqlScopedLock(SqlConnection& connection,
53 std::string_view lockName,
54 std::chrono::milliseconds timeout = std::chrono::seconds(30));
55
56 /// Non-throwing factory that returns either a held lock or a structured
57 /// `SqlLockError` describing why the acquire failed. Prefer this when
58 /// the caller wants to react programmatically to the failure reason
59 /// (timeout vs deadlock vs driver error) — for example, to retry with
60 /// backoff or to show a tailored UI message.
61 [[nodiscard]] LIGHTWEIGHT_API static std::expected<SqlScopedLock, SqlLockError> TryConstruct(
62 SqlConnection& connection, std::string_view lockName, std::chrono::milliseconds timeout = std::chrono::seconds(30));
63
64 /// Releases the lock on destruction. Release-time errors are routed to
65 /// `SqlLogger::OnWarning` rather than being silently swallowed.
66 LIGHTWEIGHT_API ~SqlScopedLock();
67
68 SqlScopedLock(SqlScopedLock const&) = delete;
69 SqlScopedLock& operator=(SqlScopedLock const&) = delete;
70
71 /// Move constructor.
72 LIGHTWEIGHT_API SqlScopedLock(SqlScopedLock&& other) noexcept;
73
74 /// Move assignment operator.
75 LIGHTWEIGHT_API SqlScopedLock& operator=(SqlScopedLock&& other) noexcept;
76
77 /// Check if the lock is currently held by this instance.
78 [[nodiscard]] bool IsLocked() const noexcept
79 {
80 return _locked;
81 }
82
83 /// Returns the lock name passed at construction.
84 [[nodiscard]] std::string_view Name() const noexcept
85 {
86 return _lockName;
87 }
88
89 /// Release the lock early.
90 ///
91 /// Automatically called in the destructor; can be invoked manually for
92 /// finer scope control. Returns the underlying `SqlLockError` if the
93 /// release round-trip fails so callers can log or surface it.
94 [[nodiscard]] LIGHTWEIGHT_API std::expected<void, SqlLockError> Release();
95
96 private:
97 /// Internal constructor used by `TryConstruct` to skip the throwing
98 /// acquire path — the caller has already inspected the handler's
99 /// `TryAcquire` result and confirmed success.
100 struct AlreadyLockedTag
101 {
102 };
103 SqlScopedLock(SqlConnection& connection,
104 std::string_view lockName,
105 SqlAdvisoryLockHandler const& handler,
106 AlreadyLockedTag /*tag*/) noexcept;
107
108 SqlConnection* _connection;
109 std::string _lockName;
110 SqlAdvisoryLockHandler const* _handler { nullptr };
111 bool _locked { false };
112};
113
114} // namespace Lightweight
Represents a connection to a SQL database.
LIGHTWEIGHT_API SqlScopedLock(SqlScopedLock &&other) noexcept
Move constructor.
bool IsLocked() const noexcept
Check if the lock is currently held by this instance.
static LIGHTWEIGHT_API std::expected< SqlScopedLock, SqlLockError > TryConstruct(SqlConnection &connection, std::string_view lockName, std::chrono::milliseconds timeout=std::chrono::seconds(30))
LIGHTWEIGHT_API SqlScopedLock(SqlConnection &connection, std::string_view lockName, std::chrono::milliseconds timeout=std::chrono::seconds(30))
LIGHTWEIGHT_API SqlScopedLock & operator=(SqlScopedLock &&other) noexcept
Move assignment operator.
LIGHTWEIGHT_API std::expected< void, SqlLockError > Release()
std::string_view Name() const noexcept
Returns the lock name passed at construction.
LIGHTWEIGHT_API ~SqlScopedLock()