21#ifndef PACS_STORAGE_BASE_REPOSITORY_HPP_INCLUDED
22#error "Do not include base_repository_impl.hpp directly. Include base_repository.hpp instead."
33template <
typename Entity,
typename PrimaryKey>
34base_repository<Entity, PrimaryKey>::base_repository(
35 std::shared_ptr<pacs_database_adapter> db,
36 std::string table_name,
37 std::string pk_column)
39 table_name_(std::
move(table_name)),
40 pk_column_(std::
move(pk_column)) {}
46template <
typename Entity,
typename PrimaryKey>
47auto base_repository<Entity, PrimaryKey>::find_by_id(PrimaryKey
id)
49 if (!db_ || !db_->is_connected()) {
50 return Result<Entity>(kcenon::common::error_info{
51 -1,
"Database not connected",
"storage"});
54 auto storage = storage_session();
55 auto builder =
storage.create_query_builder();
56 auto columns = select_columns();
58 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
59 builder.select({
"*"});
61 builder.select(columns);
64 builder.from(table_name_);
67 database_value pk_value;
68 if constexpr (std::is_integral_v<PrimaryKey>) {
69 pk_value =
static_cast<int64_t
>(
id);
70 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
73 std::ostringstream oss;
78 builder.where(pk_column_,
"=", pk_value);
81 auto query = builder.build();
82 auto result =
storage.select(query);
84 if (result.is_err()) {
85 return Result<Entity>(result.error());
88 const auto& db_result = result.value();
89 if (db_result.empty()) {
91 if constexpr (std::is_same_v<PrimaryKey, std::string>) {
94 id_str = std::to_string(
id);
96 return Result<Entity>(kcenon::common::error_info{
97 -1,
"Entity not found with id=" + id_str,
"storage"});
101 auto entity = map_row_to_entity(db_result[0]);
102 return Result<Entity>(std::move(entity));
103 }
catch (
const std::exception& e) {
104 return Result<Entity>(kcenon::common::error_info{
105 -1, std::string(
"Failed to map row to entity: ") + e.what(),
110template <
typename Entity,
typename PrimaryKey>
111auto base_repository<Entity, PrimaryKey>::find_all(
112 std::optional<size_t> limit) -> list_result_type {
113 if (!db_ || !db_->is_connected()) {
114 return list_result_type(kcenon::common::error_info{
115 -1,
"Database not connected",
"storage"});
118 auto storage = storage_session();
119 auto builder =
storage.create_query_builder();
120 auto columns = select_columns();
122 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
123 builder.select({
"*"});
125 builder.select(columns);
128 builder.from(table_name_);
130 if (limit.has_value()) {
131 builder.limit(limit.value());
134 auto query = builder.build();
135 auto result =
storage.select(query);
137 if (result.is_err()) {
138 return list_result_type(result.error());
141 std::vector<Entity> entities;
142 entities.reserve(result.value().size());
145 for (
const auto& row : result.value()) {
146 entities.push_back(map_row_to_entity(row));
148 return list_result_type(std::move(entities));
149 }
catch (
const std::exception& e) {
150 return list_result_type(kcenon::common::error_info{
151 -1, std::string(
"Failed to map rows to entities: ") + e.what(),
156template <
typename Entity,
typename PrimaryKey>
157auto base_repository<Entity, PrimaryKey>::find_where(
158 const std::string& column,
159 const std::string& op,
160 const database_value& value) -> list_result_type {
161 if (!db_ || !db_->is_connected()) {
162 return list_result_type(kcenon::common::error_info{
163 -1,
"Database not connected",
"storage"});
166 auto storage = storage_session();
167 auto builder =
storage.create_query_builder();
168 auto columns = select_columns();
170 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
171 builder.select({
"*"});
173 builder.select(columns);
176 builder.from(table_name_).where(column, op, value);
178 auto query = builder.build();
179 auto result =
storage.select(query);
181 if (result.is_err()) {
182 return list_result_type(result.error());
185 std::vector<Entity> entities;
186 entities.reserve(result.value().size());
189 for (
const auto& row : result.value()) {
190 entities.push_back(map_row_to_entity(row));
192 return list_result_type(std::move(entities));
193 }
catch (
const std::exception& e) {
194 return list_result_type(kcenon::common::error_info{
195 -1, std::string(
"Failed to map rows to entities: ") + e.what(),
200template <
typename Entity,
typename PrimaryKey>
201auto base_repository<Entity, PrimaryKey>::exists(PrimaryKey
id)
203 if (!db_ || !db_->is_connected()) {
204 return Result<bool>(kcenon::common::error_info{
205 -1,
"Database not connected",
"storage"});
208 auto storage = storage_session();
210 database_value pk_value;
211 if constexpr (std::is_integral_v<PrimaryKey>) {
212 pk_value =
static_cast<int64_t
>(
id);
213 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
216 std::ostringstream oss;
218 pk_value = oss.str();
221 auto builder =
storage.create_query_builder();
222 builder.select({pk_column_})
224 .where(pk_column_,
"=", pk_value);
226 auto result =
storage.select(builder.build());
228 if (result.is_err()) {
229 return Result<bool>(result.error());
232 return Result<bool>(!result.value().empty());
235template <
typename Entity,
typename PrimaryKey>
236auto base_repository<Entity, PrimaryKey>::count() -> Result<size_t> {
237 if (!db_ || !db_->is_connected()) {
238 return Result<size_t>(kcenon::common::error_info{
239 -1,
"Database not connected",
"storage"});
242 auto storage = storage_session();
243 auto query = std::string(
"SELECT COUNT(*) as count FROM ") + table_name_;
244 auto result =
storage.select(query);
246 if (result.is_err()) {
247 return Result<size_t>(result.error());
250 if (result.value().empty()) {
251 return Result<size_t>(
static_cast<size_t>(0));
255 const auto& row = result.value()[0];
256 auto it = row.find(
"count");
257 if (it == row.end() && !row.empty()) {
261 if (it == row.end() || it->second.empty()) {
262 return Result<size_t>(
static_cast<size_t>(0));
265 size_t count = std::stoull(it->second);
266 return Result<size_t>(count);
267 }
catch (
const std::exception& e) {
268 return Result<size_t>(kcenon::common::error_info{
269 -1, std::string(
"Failed to parse count: ") + e.what(),
278template <
typename Entity,
typename PrimaryKey>
279auto base_repository<Entity, PrimaryKey>::save(
const Entity& entity)
280 -> Result<PrimaryKey> {
281 if (has_pk(entity)) {
282 auto update_result =
update(entity);
283 if (update_result.is_err()) {
284 return Result<PrimaryKey>(update_result.error());
286 return Result<PrimaryKey>(get_pk(entity));
288 return insert(entity);
292template <
typename Entity,
typename PrimaryKey>
293auto base_repository<Entity, PrimaryKey>::insert(
const Entity& entity)
294 -> Result<PrimaryKey> {
295 if (!db_ || !db_->is_connected()) {
296 return Result<PrimaryKey>(kcenon::common::error_info{
297 -1,
"Database not connected",
"storage"});
301 auto row = entity_to_row(entity);
302 auto storage = storage_session();
303 auto builder =
storage.create_query_builder();
304 builder.insert_into(table_name_).values(row);
306 auto query = builder.build();
307 auto result =
storage.insert(query);
309 if (result.is_err()) {
310 return Result<PrimaryKey>(result.error());
314 auto rowid =
storage.last_insert_rowid();
316 if constexpr (std::is_integral_v<PrimaryKey>) {
317 return Result<PrimaryKey>(
static_cast<PrimaryKey
>(rowid));
318 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
319 return Result<PrimaryKey>(std::to_string(rowid));
321 return Result<PrimaryKey>(kcenon::common::error_info{
322 -1,
"Unsupported primary key type",
"storage"});
324 }
catch (
const std::exception& e) {
325 return Result<PrimaryKey>(kcenon::common::error_info{
326 -1, std::string(
"Failed to insert entity: ") + e.what(),
331template <
typename Entity,
typename PrimaryKey>
332auto base_repository<Entity, PrimaryKey>::update(
const Entity& entity)
334 if (!db_ || !db_->is_connected()) {
335 return VoidResult(kcenon::common::error_info{
336 -1,
"Database not connected",
"storage"});
339 if (!has_pk(entity)) {
340 return VoidResult(kcenon::common::error_info{
341 -1,
"Entity does not have a valid primary key for update",
346 auto row = entity_to_row(entity);
347 auto pk = get_pk(entity);
349 auto storage = storage_session();
350 auto builder =
storage.create_query_builder();
351 builder.update(table_name_).set(row);
354 database_value pk_value;
355 if constexpr (std::is_integral_v<PrimaryKey>) {
356 pk_value =
static_cast<int64_t
>(pk);
357 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
360 std::ostringstream oss;
362 pk_value = oss.str();
365 builder.where(pk_column_,
"=", pk_value);
367 auto query = builder.build();
368 auto result =
storage.update(query);
370 if (result.is_err()) {
371 return VoidResult(result.error());
374 if (result.value() == 0) {
375 return VoidResult(kcenon::common::error_info{
376 -1,
"No rows were updated - entity may not exist",
"storage"});
379 return kcenon::common::ok();
380 }
catch (
const std::exception& e) {
381 return VoidResult(kcenon::common::error_info{
382 -1, std::string(
"Failed to update entity: ") + e.what(),
387template <
typename Entity,
typename PrimaryKey>
388auto base_repository<Entity, PrimaryKey>::remove(PrimaryKey
id)
390 if (!db_ || !db_->is_connected()) {
391 return VoidResult(kcenon::common::error_info{
392 -1,
"Database not connected",
"storage"});
395 auto storage = storage_session();
396 auto builder =
storage.create_query_builder();
399 database_value pk_value;
400 if constexpr (std::is_integral_v<PrimaryKey>) {
401 pk_value =
static_cast<int64_t
>(
id);
402 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
405 std::ostringstream oss;
407 pk_value = oss.str();
410 builder.delete_from(table_name_).where(pk_column_,
"=", pk_value);
412 auto query = builder.build();
413 auto result =
storage.remove(query);
415 if (result.is_err()) {
416 return VoidResult(result.error());
419 if (result.value() == 0) {
420 return VoidResult(kcenon::common::error_info{
421 -1,
"No rows were deleted - entity may not exist",
"storage"});
424 return kcenon::common::ok();
427template <
typename Entity,
typename PrimaryKey>
428auto base_repository<Entity, PrimaryKey>::remove_where(
429 const std::string& column,
430 const std::string& op,
431 const database_value& value) -> Result<size_t> {
432 if (!db_ || !db_->is_connected()) {
433 return Result<size_t>(kcenon::common::error_info{
434 -1,
"Database not connected",
"storage"});
437 auto storage = storage_session();
438 auto builder =
storage.create_query_builder();
439 builder.delete_from(table_name_).where(column, op, value);
441 auto query = builder.build();
442 auto result =
storage.remove(query);
444 if (result.is_err()) {
445 return Result<size_t>(result.error());
448 return Result<size_t>(
static_cast<size_t>(result.value()));
455template <
typename Entity,
typename PrimaryKey>
456auto base_repository<Entity, PrimaryKey>::insert_batch(
457 const std::vector<Entity>& entities) -> Result<std::vector<PrimaryKey>> {
458 if (!db_ || !db_->is_connected()) {
459 return Result<std::vector<PrimaryKey>>(kcenon::common::error_info{
460 -1,
"Database not connected",
"storage"});
463 std::vector<PrimaryKey> ids;
464 ids.reserve(entities.size());
466 auto uow_result = db_->begin_unit_of_work();
467 if (uow_result.is_err()) {
468 return Result<std::vector<PrimaryKey>>(uow_result.error());
471 auto uow = std::move(uow_result.value());
472 for (
const auto& entity : entities) {
473 auto insert_result = insert(entity);
474 if (insert_result.is_err()) {
475 (void)uow.rollback();
476 return Result<std::vector<PrimaryKey>>(insert_result.error());
478 ids.push_back(insert_result.value());
481 auto commit_result = uow.commit();
482 if (commit_result.is_err()) {
483 return Result<std::vector<PrimaryKey>>(commit_result.error());
486 return Result<std::vector<PrimaryKey>>(std::move(ids));
489template <
typename Entity,
typename PrimaryKey>
490template <
typename Func>
491auto base_repository<Entity, PrimaryKey>::in_transaction(Func&& func)
492 -> std::invoke_result_t<Func> {
493 if (!db_ || !db_->is_connected()) {
494 using ResultType = std::invoke_result_t<Func>;
495 return ResultType(kcenon::common::error_info{
496 -1,
"Database not connected",
"storage"});
499 auto uow_result = db_->begin_unit_of_work();
500 if (uow_result.is_err()) {
501 using ResultType = std::invoke_result_t<Func>;
502 return ResultType(uow_result.error());
505 auto uow = std::move(uow_result.value());
507 auto result = std::forward<Func>(func)();
508 if (result.is_err()) {
509 (void)uow.rollback();
513 auto commit_result = uow.commit();
514 if (commit_result.is_err()) {
515 using ResultType = std::invoke_result_t<Func>;
516 return ResultType(commit_result.error());
520 }
catch (
const std::exception& e) {
521 (void)uow.rollback();
522 using ResultType = std::invoke_result_t<Func>;
523 return ResultType(kcenon::common::error_info{
524 -1, std::string(
"Transaction failed: ") + e.what(),
"storage"});
532template <
typename Entity,
typename PrimaryKey>
533auto base_repository<Entity, PrimaryKey>::select_columns() const
534 -> std::vector<std::
string> {
538template <
typename Entity,
typename PrimaryKey>
539auto base_repository<Entity, PrimaryKey>::query_builder()
540 -> database::query_builder {
541 return storage_session().create_query_builder();
544template <
typename Entity,
typename PrimaryKey>
545auto base_repository<Entity, PrimaryKey>::storage_session()
546 -> pacs_storage_session {
547 return db_->open_session();
550template <
typename Entity,
typename PrimaryKey>
551auto base_repository<Entity, PrimaryKey>::db()
552 -> std::shared_ptr<pacs_database_adapter> {
556template <
typename Entity,
typename PrimaryKey>
557auto base_repository<Entity, PrimaryKey>::table_name() const
558 -> const std::
string& {
562template <
typename Entity,
typename PrimaryKey>
563auto base_repository<Entity, PrimaryKey>::pk_column() const
564 -> const std::
string& {
@ move
C-MOVE move request/response.
const atna_coded_value query
Query (110112)
@ id
Implant Displaced (alternate code)
@ storage
Storage Service Class.