ORE Studio 0.0.4
Loading...
Searching...
No Matches
tenant_aware_pool.hpp
1/* -*- mode: c++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * Copyright (C) 2025 Marco Craveiro <marco.craveiro@gmail.com>
4 *
5 * This program is free software; you can redistribute it and/or modify it under
6 * the terms of the GNU General Public License as published by the Free Software
7 * Foundation; either version 3 of the License, or (at your option) any later
8 * version.
9 *
10 * This program is distributed in the hope that it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
12 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
13 * details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program; if not, write to the Free Software Foundation, Inc., 51
17 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 *
19 */
20#ifndef ORES_DATABASE_TENANT_AWARE_POOL_HPP
21#define ORES_DATABASE_TENANT_AWARE_POOL_HPP
22
23#include <mutex>
24#include <optional>
25#include <string>
26#include <vector>
27#include <sqlgen/ConnectionPool.hpp>
28#include <sqlgen/postgres.hpp>
29#include <boost/uuid/uuid.hpp>
30#include <boost/uuid/uuid_io.hpp>
31#include "ores.logging/make_logger.hpp"
32#include "ores.utility/uuid/tenant_id.hpp"
33
34namespace ores::database {
35
48template <class Connection>
50private:
51 inline static std::string_view logger_name =
52 "ores.database.domain.tenant_aware_pool";
53
54 [[nodiscard]] static auto& lg() {
55 using namespace ores::logging;
56 static auto instance = make_logger(logger_name);
57 return instance;
58 }
59
60public:
64 tenant_aware_pool(sqlgen::ConnectionPool<Connection> pool,
65 sqlgen::postgres::Credentials credentials,
67 std::string actor = "",
68 std::string service_account = "")
69 : pool_(std::move(pool)), credentials_(std::move(credentials)),
70 pool_size_(pool_.size()),
71 reconnect_mutex_(std::make_shared<std::mutex>()),
72 tenant_id_(std::move(tenant_id)),
73 actor_(std::move(actor)),
74 service_account_(std::move(service_account)) {}
75
79 tenant_aware_pool(sqlgen::ConnectionPool<Connection> pool,
80 sqlgen::postgres::Credentials credentials,
82 boost::uuids::uuid party_id,
83 std::vector<boost::uuids::uuid> visible_party_ids,
84 std::string actor = "",
85 std::string service_account = "")
86 : pool_(std::move(pool)), credentials_(std::move(credentials)),
87 pool_size_(pool_.size()),
88 reconnect_mutex_(std::make_shared<std::mutex>()),
89 tenant_id_(std::move(tenant_id)),
90 party_id_(party_id),
91 visible_party_ids_(std::move(visible_party_ids)),
92 actor_(std::move(actor)),
93 service_account_(std::move(service_account)) {}
94
102 sqlgen::Result<sqlgen::Ref<sqlgen::Session<Connection>>> acquire() noexcept {
103 using namespace ores::logging;
104
105 auto session_result = pool_.acquire();
106 if (!session_result) {
107 return session_result;
108 }
109
110 // Speculatively rollback any aborted transaction left by a previous
111 // failed operation. PostgreSQL accepts ROLLBACK even when no
112 // transaction is active, so this is always safe.
113 // If ROLLBACK itself fails the connection is dead — rebuild the pool.
114 bool needs_rebuild = false;
115 std::string rollback_error;
116 {
117 auto rollback_result = (*session_result)->execute("ROLLBACK");
118 if (!rollback_result) {
119 needs_rebuild = true;
120 rollback_error = rollback_result.error().what();
121 }
122 } // session_result still held here intentionally
123
124 if (needs_rebuild) {
125 BOOST_LOG_SEV(lg(), warn)
126 << "Pool connection dead (ROLLBACK failed: "
127 << rollback_error << "). Rebuilding pool...";
128 // Drop session so the connection flag is released before we
129 // replace the pool (avoids use-after-free of the old pool entry).
130 session_result = pool_.acquire(); // re-acquire to replace below
131 {
132 std::lock_guard lock(*reconnect_mutex_);
133 sqlgen::ConnectionPoolConfig cfg{
134 .size = pool_size_,
135 .num_attempts = 3,
136 .wait_time_in_seconds = 1
137 };
138 auto new_pool = sqlgen::make_connection_pool<Connection>(
139 cfg, credentials_);
140 if (new_pool) {
141 pool_ = std::move(*new_pool);
142 BOOST_LOG_SEV(lg(), info) << "Pool rebuilt successfully.";
143 } else {
144 BOOST_LOG_SEV(lg(), error)
145 << "Pool rebuild failed: " << new_pool.error().what();
146 return sqlgen::error("Pool rebuild failed: " +
147 std::string(new_pool.error().what()));
148 }
149 }
150 session_result = pool_.acquire();
151 if (!session_result) return session_result;
152 (*session_result)->execute("ROLLBACK"); // best-effort on fresh conn
153 }
154
155 // Force UTC for all timestamp operations on this connection.
156 // PostgreSQL returns timestamptz values as "YYYY-MM-DD HH:MM:SS+00"
157 // when the session timezone is UTC, which from_iso8601_utc accepts.
158 auto tz_result = (*session_result)->execute(
159 "SELECT set_config('TimeZone', 'UTC', false)");
160 if (!tz_result) {
161 return sqlgen::error("Failed to set session timezone to UTC: " +
162 std::string(tz_result.error().what()));
163 }
164
165 const auto tenant_id_str = tenant_id_.to_string();
166 const std::string sql =
167 "SELECT set_config('app.current_tenant_id', '" +
168 tenant_id_str + "', false)";
169
170 auto exec_result = (*session_result)->execute(sql);
171 if (!exec_result) {
172 return sqlgen::error("Failed to set tenant context: " +
173 std::string(exec_result.error().what()));
174 }
175
176 BOOST_LOG_SEV(lg(), debug) << "Set tenant context to: " << tenant_id_str;
177
178 // Set party context if available
179 if (party_id_.has_value()) {
180 const auto party_id_str = boost::uuids::to_string(*party_id_);
181 const std::string party_sql =
182 "SELECT set_config('app.current_party_id', '" +
183 party_id_str + "', false)";
184
185 auto party_result = (*session_result)->execute(party_sql);
186 if (!party_result) {
187 return sqlgen::error("Failed to set party context: " +
188 std::string(party_result.error().what()));
189 }
190
191 BOOST_LOG_SEV(lg(), debug) << "Set party context to: "
192 << party_id_str;
193 }
194
195 // Set visible party IDs if available
196 if (!visible_party_ids_.empty()) {
197 std::string ids_str = "{";
198 for (std::size_t i = 0; i < visible_party_ids_.size(); ++i) {
199 if (i > 0) ids_str += ",";
200 ids_str += boost::uuids::to_string(visible_party_ids_[i]);
201 }
202 ids_str += "}";
203
204 const std::string vis_sql =
205 "SELECT set_config('app.visible_party_ids', '" +
206 ids_str + "', false)";
207
208 auto vis_result = (*session_result)->execute(vis_sql);
209 if (!vis_result) {
210 return sqlgen::error("Failed to set visible party IDs: " +
211 std::string(vis_result.error().what()));
212 }
213
214 BOOST_LOG_SEV(lg(), debug) << "Set visible party IDs ("
215 << visible_party_ids_.size()
216 << " parties)";
217 }
218
219 // Set current actor (username) if available.
220 if (!actor_.empty()) {
221 const std::string actor_sql =
222 "SELECT set_config('app.current_actor', '" +
223 actor_ + "', false)";
224
225 auto actor_result = (*session_result)->execute(actor_sql);
226 if (!actor_result) {
227 return sqlgen::error("Failed to set actor context: " +
228 std::string(actor_result.error().what()));
229 }
230
231 BOOST_LOG_SEV(lg(), debug) << "Set actor context to: " << actor_;
232 }
233
234 // Set current service (service account) if available.
235 // This is used by DB triggers to stamp performed_by.
236 if (!service_account_.empty()) {
237 const std::string svc_sql =
238 "SELECT set_config('app.current_service', '" +
239 service_account_ + "', false)";
240 auto svc_result = (*session_result)->execute(svc_sql);
241 if (!svc_result) {
242 return sqlgen::error("Failed to set service context: " +
243 std::string(svc_result.error().what()));
244 }
245
246 BOOST_LOG_SEV(lg(), debug) << "Set service context to: "
247 << service_account_;
248 }
249
250 return session_result;
251 }
252
256 const utility::uuid::tenant_id& tenant_id() const { return tenant_id_; }
257
261 std::optional<boost::uuids::uuid> party_id() const { return party_id_; }
262
266 const std::vector<boost::uuids::uuid>& visible_party_ids() const {
267 return visible_party_ids_;
268 }
269
273 const std::string& actor() const { return actor_; }
274
278 const std::string& service_account() const { return service_account_; }
279
283 const sqlgen::ConnectionPool<Connection>& underlying_pool() const {
284 return pool_;
285 }
286
290 size_t available() const { return pool_.available(); }
291
295 size_t size() const { return pool_.size(); }
296
297private:
298 sqlgen::ConnectionPool<Connection> pool_;
299 sqlgen::postgres::Credentials credentials_;
300 std::size_t pool_size_;
301 std::shared_ptr<std::mutex> reconnect_mutex_;
302 utility::uuid::tenant_id tenant_id_;
303 std::optional<boost::uuids::uuid> party_id_;
304 std::vector<boost::uuids::uuid> visible_party_ids_;
305 std::string actor_;
306 std::string service_account_;
307};
308
309}
310
311namespace sqlgen {
312
313template <class Connection>
314Result<Ref<Session<Connection>>> session(
316 return pool.acquire();
317}
318
319}
320
321#endif
STL namespace.
Implements logging infrastructure for ORE Studio.
Definition boost_severity.hpp:28
A connection pool wrapper that sets tenant and party context on acquire.
Definition tenant_aware_pool.hpp:49
sqlgen::Result< sqlgen::Ref< sqlgen::Session< Connection > > > acquire() noexcept
Acquires a session and sets the tenant (and party) context.
Definition tenant_aware_pool.hpp:102
const std::string & service_account() const
Gets the current service account, if set.
Definition tenant_aware_pool.hpp:278
size_t size() const
Gets the total number of connections in the pool.
Definition tenant_aware_pool.hpp:295
const sqlgen::ConnectionPool< Connection > & underlying_pool() const
Gets the underlying connection pool.
Definition tenant_aware_pool.hpp:283
std::optional< boost::uuids::uuid > party_id() const
Gets the current party ID, if set.
Definition tenant_aware_pool.hpp:261
tenant_aware_pool(sqlgen::ConnectionPool< Connection > pool, sqlgen::postgres::Credentials credentials, utility::uuid::tenant_id tenant_id, boost::uuids::uuid party_id, std::vector< boost::uuids::uuid > visible_party_ids, std::string actor="", std::string service_account="")
Constructs a tenant-and-party-aware pool wrapper.
Definition tenant_aware_pool.hpp:79
const std::vector< boost::uuids::uuid > & visible_party_ids() const
Gets the visible party IDs.
Definition tenant_aware_pool.hpp:266
size_t available() const
Gets the number of available connections.
Definition tenant_aware_pool.hpp:290
tenant_aware_pool(sqlgen::ConnectionPool< Connection > pool, sqlgen::postgres::Credentials credentials, utility::uuid::tenant_id tenant_id, std::string actor="", std::string service_account="")
Constructs a tenant-aware pool wrapper (tenant-only).
Definition tenant_aware_pool.hpp:64
const std::string & actor() const
Gets the current actor (username), if set.
Definition tenant_aware_pool.hpp:273
const utility::uuid::tenant_id & tenant_id() const
Gets the current tenant ID.
Definition tenant_aware_pool.hpp:256
A strongly-typed wrapper around a UUID representing a tenant identifier.
Definition tenant_id.hpp:66
std::string to_string() const
Converts the tenant_id to its string representation.
Definition tenant_id.cpp:81