21#ifndef PACS_STORAGE_BASE_REPOSITORY_HPP_INCLUDED
22#error "Do not include base_repository_impl.hpp directly. Include base_repository.hpp instead."
38template <
typename Entity,
typename PrimaryKey>
39base_repository<Entity, PrimaryKey>::base_repository(
40 std::shared_ptr<pacs_database_adapter> db,
41 std::string table_name,
42 std::string pk_column)
44 table_name_(std::
move(table_name)),
45 pk_column_(std::
move(pk_column)) {}
51template <
typename Entity,
typename PrimaryKey>
52auto base_repository<Entity, PrimaryKey>::find_by_id(PrimaryKey
id)
54 if (!db_ || !db_->is_connected()) {
55 return Result<Entity>(kcenon::common::error_info{
56 -1,
"Database not connected",
"storage"});
59 auto storage = storage_session();
60 auto builder =
storage.create_query_builder();
61 auto columns = select_columns();
63 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
64 builder.select({
"*"});
66 builder.select(columns);
69 builder.from(table_name_);
72 database_value pk_value;
73 if constexpr (std::is_integral_v<PrimaryKey>) {
74 pk_value =
static_cast<int64_t
>(
id);
75 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
78 std::ostringstream oss;
83 builder.where(pk_column_,
"=", pk_value);
86 auto query = builder.build();
87 auto result =
storage.select(query);
89 if (result.is_err()) {
90 return Result<Entity>(result.error());
93 const auto& db_result = result.value();
94 if (db_result.empty()) {
96 if constexpr (std::is_same_v<PrimaryKey, std::string>) {
99 id_str = std::to_string(
id);
101 return Result<Entity>(kcenon::common::error_info{
102 -1,
"Entity not found with id=" + id_str,
"storage"});
106 auto entity = map_row_to_entity(db_result[0]);
107 return Result<Entity>(std::move(entity));
108 }
catch (
const std::exception& e) {
109 return Result<Entity>(kcenon::common::error_info{
110 -1, std::string(
"Failed to map row to entity: ") + e.what(),
115template <
typename Entity,
typename PrimaryKey>
116auto base_repository<Entity, PrimaryKey>::find_all(
117 std::optional<size_t> limit) -> list_result_type {
118 if (!db_ || !db_->is_connected()) {
119 return list_result_type(kcenon::common::error_info{
120 -1,
"Database not connected",
"storage"});
123 auto storage = storage_session();
124 auto builder =
storage.create_query_builder();
125 auto columns = select_columns();
127 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
128 builder.select({
"*"});
130 builder.select(columns);
133 builder.from(table_name_);
135 if (limit.has_value()) {
136 builder.limit(limit.value());
139 auto query = builder.build();
140 auto result =
storage.select(query);
142 if (result.is_err()) {
143 return list_result_type(result.error());
146 std::vector<Entity> entities;
147 entities.reserve(result.value().size());
150 for (
const auto& row : result.value()) {
151 entities.push_back(map_row_to_entity(row));
153 return list_result_type(std::move(entities));
154 }
catch (
const std::exception& e) {
155 return list_result_type(kcenon::common::error_info{
156 -1, std::string(
"Failed to map rows to entities: ") + e.what(),
161template <
typename Entity,
typename PrimaryKey>
162auto base_repository<Entity, PrimaryKey>::find_where(
163 const std::string& column,
164 const std::string& op,
165 const database_value& value) -> list_result_type {
166 if (!db_ || !db_->is_connected()) {
167 return list_result_type(kcenon::common::error_info{
168 -1,
"Database not connected",
"storage"});
171 auto storage = storage_session();
172 auto builder =
storage.create_query_builder();
173 auto columns = select_columns();
175 if (
columns.empty() || (
columns.size() == 1 && columns[0] ==
"*")) {
176 builder.select({
"*"});
178 builder.select(columns);
181 builder.from(table_name_).where(column, op, value);
183 auto query = builder.build();
184 auto result =
storage.select(query);
186 if (result.is_err()) {
187 return list_result_type(result.error());
190 std::vector<Entity> entities;
191 entities.reserve(result.value().size());
194 for (
const auto& row : result.value()) {
195 entities.push_back(map_row_to_entity(row));
197 return list_result_type(std::move(entities));
198 }
catch (
const std::exception& e) {
199 return list_result_type(kcenon::common::error_info{
200 -1, std::string(
"Failed to map rows to entities: ") + e.what(),
205template <
typename Entity,
typename PrimaryKey>
206auto base_repository<Entity, PrimaryKey>::exists(PrimaryKey
id)
208 if (!db_ || !db_->is_connected()) {
209 return Result<bool>(kcenon::common::error_info{
210 -1,
"Database not connected",
"storage"});
213 auto storage = storage_session();
215 database_value pk_value;
216 if constexpr (std::is_integral_v<PrimaryKey>) {
217 pk_value =
static_cast<int64_t
>(
id);
218 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
221 std::ostringstream oss;
223 pk_value = oss.str();
226 auto builder =
storage.create_query_builder();
227 builder.select({pk_column_})
229 .where(pk_column_,
"=", pk_value);
231 auto result =
storage.select(builder.build());
233 if (result.is_err()) {
234 return Result<bool>(result.error());
237 return Result<bool>(!result.value().empty());
240template <
typename Entity,
typename PrimaryKey>
241auto base_repository<Entity, PrimaryKey>::count() -> Result<size_t> {
242 if (!db_ || !db_->is_connected()) {
243 return Result<size_t>(kcenon::common::error_info{
244 -1,
"Database not connected",
"storage"});
247 auto storage = storage_session();
248 auto query = std::string(
"SELECT COUNT(*) as count FROM ") + table_name_;
249 auto result =
storage.select(query);
251 if (result.is_err()) {
252 return Result<size_t>(result.error());
255 if (result.value().empty()) {
256 return Result<size_t>(
static_cast<size_t>(0));
260 const auto& row = result.value()[0];
261 auto it = row.find(
"count");
262 if (it == row.end() && !row.empty()) {
266 if (it == row.end() || it->second.empty()) {
267 return Result<size_t>(
static_cast<size_t>(0));
270 size_t count = std::stoull(it->second);
271 return Result<size_t>(count);
272 }
catch (
const std::exception& e) {
273 return Result<size_t>(kcenon::common::error_info{
274 -1, std::string(
"Failed to parse count: ") + e.what(),
283template <
typename Entity,
typename PrimaryKey>
284auto base_repository<Entity, PrimaryKey>::save(
const Entity& entity)
285 -> Result<PrimaryKey> {
286 if (has_pk(entity)) {
287 auto update_result =
update(entity);
288 if (update_result.is_err()) {
289 return Result<PrimaryKey>(update_result.error());
291 return Result<PrimaryKey>(get_pk(entity));
293 return insert(entity);
297template <
typename Entity,
typename PrimaryKey>
298auto base_repository<Entity, PrimaryKey>::insert(
const Entity& entity)
299 -> Result<PrimaryKey> {
300 if (!db_ || !db_->is_connected()) {
301 return Result<PrimaryKey>(kcenon::common::error_info{
302 -1,
"Database not connected",
"storage"});
306 auto row = entity_to_row(entity);
307 auto storage = storage_session();
308 auto builder =
storage.create_query_builder();
309 builder.insert_into(table_name_).values(row);
311 auto query = builder.build();
312 auto result =
storage.insert(query);
314 if (result.is_err()) {
315 return Result<PrimaryKey>(result.error());
319 auto rowid =
storage.last_insert_rowid();
321 if constexpr (std::is_integral_v<PrimaryKey>) {
322 return Result<PrimaryKey>(
static_cast<PrimaryKey
>(rowid));
323 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
324 return Result<PrimaryKey>(std::to_string(rowid));
326 return Result<PrimaryKey>(kcenon::common::error_info{
327 -1,
"Unsupported primary key type",
"storage"});
329 }
catch (
const std::exception& e) {
330 return Result<PrimaryKey>(kcenon::common::error_info{
331 -1, std::string(
"Failed to insert entity: ") + e.what(),
336template <
typename Entity,
typename PrimaryKey>
337auto base_repository<Entity, PrimaryKey>::update(
const Entity& entity)
339 if (!db_ || !db_->is_connected()) {
340 return VoidResult(kcenon::common::error_info{
341 -1,
"Database not connected",
"storage"});
344 if (!has_pk(entity)) {
345 return VoidResult(kcenon::common::error_info{
346 -1,
"Entity does not have a valid primary key for update",
351 auto row = entity_to_row(entity);
352 auto pk = get_pk(entity);
354 auto storage = storage_session();
355 auto builder =
storage.create_query_builder();
356 builder.update(table_name_).set(row);
359 database_value pk_value;
360 if constexpr (std::is_integral_v<PrimaryKey>) {
361 pk_value =
static_cast<int64_t
>(pk);
362 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
365 std::ostringstream oss;
367 pk_value = oss.str();
370 builder.where(pk_column_,
"=", pk_value);
372 auto query = builder.build();
373 auto result =
storage.update(query);
375 if (result.is_err()) {
376 return VoidResult(result.error());
379 if (result.value() == 0) {
380 return VoidResult(kcenon::common::error_info{
381 -1,
"No rows were updated - entity may not exist",
"storage"});
384 return kcenon::common::ok();
385 }
catch (
const std::exception& e) {
386 return VoidResult(kcenon::common::error_info{
387 -1, std::string(
"Failed to update entity: ") + e.what(),
392template <
typename Entity,
typename PrimaryKey>
393auto base_repository<Entity, PrimaryKey>::remove(PrimaryKey
id)
395 if (!db_ || !db_->is_connected()) {
396 return VoidResult(kcenon::common::error_info{
397 -1,
"Database not connected",
"storage"});
400 auto storage = storage_session();
401 auto builder =
storage.create_query_builder();
404 database_value pk_value;
405 if constexpr (std::is_integral_v<PrimaryKey>) {
406 pk_value =
static_cast<int64_t
>(
id);
407 }
else if constexpr (std::is_same_v<PrimaryKey, std::string>) {
410 std::ostringstream oss;
412 pk_value = oss.str();
415 builder.delete_from(table_name_).where(pk_column_,
"=", pk_value);
417 auto query = builder.build();
418 auto result =
storage.remove(query);
420 if (result.is_err()) {
421 return VoidResult(result.error());
424 if (result.value() == 0) {
425 return VoidResult(kcenon::common::error_info{
426 -1,
"No rows were deleted - entity may not exist",
"storage"});
429 return kcenon::common::ok();
432template <
typename Entity,
typename PrimaryKey>
433auto base_repository<Entity, PrimaryKey>::remove_where(
434 const std::string& column,
435 const std::string& op,
436 const database_value& value) -> Result<size_t> {
437 if (!db_ || !db_->is_connected()) {
438 return Result<size_t>(kcenon::common::error_info{
439 -1,
"Database not connected",
"storage"});
442 auto storage = storage_session();
443 auto builder =
storage.create_query_builder();
444 builder.delete_from(table_name_).where(column, op, value);
446 auto query = builder.build();
447 auto result =
storage.remove(query);
449 if (result.is_err()) {
450 return Result<size_t>(result.error());
453 return Result<size_t>(
static_cast<size_t>(result.value()));
460template <
typename Entity,
typename PrimaryKey>
461auto base_repository<Entity, PrimaryKey>::insert_batch(
462 const std::vector<Entity>& entities) -> Result<std::vector<PrimaryKey>> {
463 if (!db_ || !db_->is_connected()) {
464 return Result<std::vector<PrimaryKey>>(kcenon::common::error_info{
465 -1,
"Database not connected",
"storage"});
468 std::vector<PrimaryKey> ids;
469 ids.reserve(entities.size());
471 auto uow_result = db_->begin_unit_of_work();
472 if (uow_result.is_err()) {
473 return Result<std::vector<PrimaryKey>>(uow_result.error());
476 auto uow = std::move(uow_result.value());
477 for (
const auto& entity : entities) {
478 auto insert_result = insert(entity);
479 if (insert_result.is_err()) {
480 (void)uow.rollback();
481 return Result<std::vector<PrimaryKey>>(insert_result.error());
483 ids.push_back(insert_result.value());
486 auto commit_result = uow.commit();
487 if (commit_result.is_err()) {
488 return Result<std::vector<PrimaryKey>>(commit_result.error());
491 return Result<std::vector<PrimaryKey>>(std::move(ids));
494template <
typename Entity,
typename PrimaryKey>
495template <
typename Func>
496auto base_repository<Entity, PrimaryKey>::in_transaction(Func&& func)
497 -> std::invoke_result_t<Func> {
498 if (!db_ || !db_->is_connected()) {
499 using ResultType = std::invoke_result_t<Func>;
500 return ResultType(kcenon::common::error_info{
501 -1,
"Database not connected",
"storage"});
504 auto uow_result = db_->begin_unit_of_work();
505 if (uow_result.is_err()) {
506 using ResultType = std::invoke_result_t<Func>;
507 return ResultType(uow_result.error());
510 auto uow = std::move(uow_result.value());
512 auto result = std::forward<Func>(func)();
513 if (result.is_err()) {
514 (void)uow.rollback();
518 auto commit_result = uow.commit();
519 if (commit_result.is_err()) {
520 using ResultType = std::invoke_result_t<Func>;
521 return ResultType(commit_result.error());
525 }
catch (
const std::exception& e) {
526 (void)uow.rollback();
527 using ResultType = std::invoke_result_t<Func>;
528 return ResultType(kcenon::common::error_info{
529 -1, std::string(
"Transaction failed: ") + e.what(),
"storage"});
537template <
typename Entity,
typename PrimaryKey>
538auto base_repository<Entity, PrimaryKey>::select_columns() const
539 -> std::vector<std::
string> {
543template <
typename Entity,
typename PrimaryKey>
544auto base_repository<Entity, PrimaryKey>::query_builder()
545 -> database::query_builder {
546 return storage_session().create_query_builder();
549template <
typename Entity,
typename PrimaryKey>
550auto base_repository<Entity, PrimaryKey>::storage_session()
551 -> pacs_storage_session {
552 return db_->open_session();
555template <
typename Entity,
typename PrimaryKey>
556auto base_repository<Entity, PrimaryKey>::db()
557 -> std::shared_ptr<pacs_database_adapter> {
561template <
typename Entity,
typename PrimaryKey>
562auto base_repository<Entity, PrimaryKey>::table_name() const
563 -> const std::
string& {
567template <
typename Entity,
typename PrimaryKey>
568auto base_repository<Entity, PrimaryKey>::pk_column() const
569 -> 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.