17#ifdef PACS_WITH_DATABASE_SYSTEM
29using kcenon::common::make_error;
30using kcenon::common::ok;
36struct pacs_database_adapter::impl {
38 std::unique_ptr<database::integrated::unified_database_system> db;
41 sqlite3* native_sqlite_db{
nullptr};
44 bool use_native_sqlite{
false};
47 database::database_types db_type{database::database_types::sqlite};
50 std::string connection_string;
53 bool in_transaction{
false};
56 std::string last_error_msg;
59 int64_t last_rowid{0};
63 impl(database::database_types type, std::string conn_str)
64 : db_type(type), connection_string(std::
move(conn_str)) {}
76auto to_backend_type(database::database_types type)
77 -> database::integrated::backend_type {
79 case database::database_types::postgres:
80 return database::integrated::backend_type::postgres;
81 case database::database_types::sqlite:
82 return database::integrated::backend_type::sqlite;
83 case database::database_types::mongodb:
84 return database::integrated::backend_type::mongodb;
85 case database::database_types::redis:
86 return database::integrated::backend_type::redis;
88 return database::integrated::backend_type::sqlite;
92auto is_prefixed_sqlite_connection_string(std::string_view connection_string)
94 constexpr std::array<std::string_view, 3> prefixes = {
"dbname=",
"db=",
97 for (
const auto prefix : prefixes) {
98 if (connection_string.rfind(prefix, 0) == 0) {
106auto normalize_connection_string(database::database_types type,
107 std::string_view connection_string)
109 if (type != database::database_types::sqlite) {
110 return std::string(connection_string);
113 if (connection_string.empty()) {
114 return "dbname=:memory:";
117 if (is_prefixed_sqlite_connection_string(connection_string)) {
118 return std::string(connection_string);
121 return kcenon::pacs::compat::format(
"dbname={}", connection_string);
124auto extract_sqlite_connection_target(std::string_view connection_string)
126 constexpr std::array<std::string_view, 3> prefixes = {
"dbname=",
"db=",
129 for (
const auto prefix : prefixes) {
130 if (connection_string.rfind(prefix, 0) == 0) {
131 return std::string(connection_string.substr(prefix.size()));
135 return std::string(connection_string);
138auto should_use_native_sqlite(std::string_view connection_string) ->
bool {
139 const auto target = extract_sqlite_connection_target(connection_string);
140 return !target.empty() && target !=
":memory:";
147 const database::integrated::query_result& src) -> database_result {
148 database_result result;
149 result.affected_rows = src.affected_rows;
150 result.execution_time = src.execution_time;
152 result.rows.reserve(src.rows.size());
153 for (
const auto& src_row : src.rows) {
155 for (
const auto& [key, value] : src_row) {
158 result.rows.push_back(std::move(row));
167auto make_void_error(
int code,
const std::string& message,
168 const std::string& module) -> VoidResult {
169 return VoidResult(kcenon::common::error_info{
code, message, module});
172auto sqlite_error_message(sqlite3* db, std::string_view fallback)
175 if (
const auto* message = sqlite3_errmsg(db); message !=
nullptr) {
176 return std::string(message);
180 return std::string(fallback);
183auto execute_native_sqlite(sqlite3* db,
const std::string& query)
185 char* error_message =
nullptr;
187 sqlite3_exec(db,
query.c_str(),
nullptr,
nullptr, &error_message);
188 if (rc != SQLITE_OK) {
189 const std::string message =
190 error_message !=
nullptr ? std::string(error_message)
191 : sqlite_error_message(db,
"SQLite execution failed");
192 sqlite3_free(error_message);
193 return make_void_error(rc, message,
"storage");
199auto select_native_sqlite(sqlite3* db,
const std::string& query)
201 sqlite3_stmt* statement =
nullptr;
202 const auto started = std::chrono::steady_clock::now();
204 sqlite3_prepare_v2(db,
query.c_str(), -1, &statement,
nullptr);
205 if (rc != SQLITE_OK) {
206 return make_error<database_result>(
207 rc, sqlite_error_message(db,
"Failed to prepare SQLite query"),
211 database_result result;
213 const auto step_rc = sqlite3_step(statement);
214 if (step_rc == SQLITE_ROW) {
216 const auto column_count = sqlite3_column_count(statement);
217 for (
int i = 0; i < column_count; ++i) {
218 const auto* column_name = sqlite3_column_name(statement, i);
219 const auto* column_value = sqlite3_column_text(statement, i);
220 row[column_name !=
nullptr ? column_name :
""] =
221 column_value !=
nullptr
222 ?
reinterpret_cast<const char*
>(column_value)
225 result.rows.push_back(std::move(row));
229 sqlite3_finalize(statement);
230 if (step_rc != SQLITE_DONE) {
231 return make_error<database_result>(
233 sqlite_error_message(db,
"Failed to execute SQLite query"),
237 result.affected_rows = result.rows.size();
238 result.execution_time = std::chrono::duration_cast<std::chrono::microseconds>(
239 std::chrono::steady_clock::now() -
started);
240 return Result<database_result>::ok(std::move(result));
244auto mutate_native_sqlite(sqlite3* db,
const std::string& query)
246 auto result = execute_native_sqlite(db, query);
247 if (result.is_err()) {
248 return make_error<uint64_t>(result.error().code, result.error().message,
249 result.error().module);
252 return Result<uint64_t>::ok(
253 static_cast<uint64_t
>(sqlite3_changes(db)));
262pacs_storage_session::pacs_storage_session(
263 pacs_database_adapter& adapter) noexcept
264 : adapter_(&adapter) {}
266auto pacs_storage_session::create_query_builder() const
267 -> database::query_builder {
268 return adapter_->create_query_builder();
271auto pacs_storage_session::select(
const std::string& query)
273 return adapter_->run_select(query);
276auto pacs_storage_session::insert(
const std::string& query)
278 return adapter_->run_insert(query);
281auto pacs_storage_session::update(
const std::string& query)
283 return adapter_->run_update(query);
286auto pacs_storage_session::remove(
const std::string& query)
288 return adapter_->run_remove(query);
291auto pacs_storage_session::execute(
const std::string& query) -> VoidResult {
292 return adapter_->run_execute(query);
295auto pacs_storage_session::last_insert_rowid() const -> int64_t {
296 return adapter_->last_insert_rowid();
300 return adapter_->begin_unit_of_work();
307pacs_unit_of_work::pacs_unit_of_work(
308 pacs_database_adapter& adapter,
bool active) noexcept
309 : adapter_(&adapter), active_(active) {}
311pacs_unit_of_work::~pacs_unit_of_work() {
312 if (active_ && adapter_ !=
nullptr) {
313 (void)adapter_->rollback_internal();
317pacs_unit_of_work::pacs_unit_of_work(pacs_unit_of_work&& other) noexcept
318 : adapter_(
other.adapter_), active_(
other.active_) {
319 other.adapter_ =
nullptr;
320 other.active_ =
false;
323auto pacs_unit_of_work::operator=(pacs_unit_of_work&& other)
noexcept
324 -> pacs_unit_of_work& {
325 if (
this == &other) {
329 if (active_ && adapter_ !=
nullptr) {
330 (void)adapter_->rollback_internal();
333 adapter_ =
other.adapter_;
334 active_ =
other.active_;
335 other.adapter_ =
nullptr;
336 other.active_ =
false;
340auto pacs_unit_of_work::create_query_builder() const -> database::query_builder {
341 return adapter_->create_query_builder();
344auto pacs_unit_of_work::select(
const std::string& query)
346 return adapter_->run_select(query);
349auto pacs_unit_of_work::insert(
const std::string& query) ->
Result<uint64_t> {
350 return adapter_->run_insert(query);
353auto pacs_unit_of_work::update(
const std::string& query) ->
Result<uint64_t> {
354 return adapter_->run_update(query);
357auto pacs_unit_of_work::remove(
const std::string& query) ->
Result<uint64_t> {
358 return adapter_->run_remove(query);
361auto pacs_unit_of_work::execute(
const std::string& query) -> VoidResult {
362 return adapter_->run_execute(query);
365auto pacs_unit_of_work::last_insert_rowid() const -> int64_t {
366 return adapter_ !=
nullptr ? adapter_->last_insert_rowid() : 0;
369auto pacs_unit_of_work::commit() -> VoidResult {
370 if (!active_ || adapter_ ==
nullptr) {
371 return make_void_error(-1,
"Unit of work not active",
"storage");
374 auto result = adapter_->commit_internal();
375 if (result.is_ok()) {
381auto pacs_unit_of_work::rollback() -> VoidResult {
382 if (!active_ || adapter_ ==
nullptr) {
386 auto result = adapter_->rollback_internal();
387 if (result.is_ok()) {
393auto pacs_unit_of_work::is_active() const noexcept ->
bool {
401pacs_database_adapter::pacs_database_adapter(
402 const std::filesystem::path& db_path)
403 : impl_(std::make_unique<impl>(
404 database::database_types::sqlite,
405 normalize_connection_string(database::database_types::sqlite,
406 db_path.string()))) {}
408pacs_database_adapter::pacs_database_adapter(
409 database::database_types type,
const std::string& connection_string)
410 : impl_(std::make_unique<impl>(
411 type, normalize_connection_string(type, connection_string))) {}
413pacs_database_adapter::~pacs_database_adapter() {
418 if (impl_->use_native_sqlite && impl_->native_sqlite_db !=
nullptr) {
419 if (impl_->in_transaction) {
420 (void)execute_native_sqlite(impl_->native_sqlite_db,
"ROLLBACK");
422 sqlite3_close(impl_->native_sqlite_db);
423 impl_->native_sqlite_db =
nullptr;
427 if (impl_->db && impl_->db->is_connected()) {
428 if (impl_->in_transaction) {
429 (void)impl_->db->execute(
"ROLLBACK");
431 (void)impl_->db->disconnect();
435pacs_database_adapter::pacs_database_adapter(
436 pacs_database_adapter&&) noexcept = default;
438auto pacs_database_adapter::operator=(pacs_database_adapter&&) noexcept
439 -> pacs_database_adapter& = default;
445auto pacs_database_adapter::connect() -> VoidResult {
447 return make_void_error(-1,
"Adapter not initialized",
"storage");
450 if (impl_->db_type == database::database_types::sqlite &&
451 should_use_native_sqlite(impl_->connection_string)) {
453 extract_sqlite_connection_target(impl_->connection_string);
454 const auto rc = sqlite3_open_v2(
455 target.c_str(), &impl_->native_sqlite_db,
456 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
458 if (rc != SQLITE_OK) {
459 const auto message = sqlite_error_message(
460 impl_->native_sqlite_db,
"Failed to open SQLite database");
461 if (impl_->native_sqlite_db != nullptr) {
462 sqlite3_close(impl_->native_sqlite_db);
463 impl_->native_sqlite_db = nullptr;
465 impl_->last_error_msg = message;
466 return make_void_error(
467 rc, kcenon::pacs::compat::format(
"Failed to connect: {}", message),
471 (
void)sqlite3_busy_timeout(impl_->native_sqlite_db, 5000);
473 execute_native_sqlite(impl_->native_sqlite_db,
"PRAGMA foreign_keys = ON");
474 if (pragma_result.is_err()) {
475 impl_->last_error_msg = pragma_result.error().message;
476 sqlite3_close(impl_->native_sqlite_db);
477 impl_->native_sqlite_db = nullptr;
478 return pragma_result;
481 impl_->use_native_sqlite =
true;
482 impl_->last_error_msg.clear();
488 auto database_result = compat::factory_result(
489 database::integrated::unified_database_system::create_builder()
490 .set_backend(to_backend_type(impl_->db_type))
491 .enable_logging(database::integrated::db_log_level::warning)
493 if (database_result.is_err()) {
494 impl_->last_error_msg = database_result.error().message;
495 return make_void_error(database_result.error().code,
496 database_result.error().message,
"storage");
498 impl_->db = std::move(database_result.value());
502 impl_->db->connect(to_backend_type(impl_->db_type),
503 impl_->connection_string);
505 if (result.is_err()) {
506 impl_->last_error_msg = result.error().message;
507 return make_void_error(
509 kcenon::pacs::compat::format(
"Failed to connect: {}",
510 result.error().message),
514 impl_->last_error_msg.clear();
516 }
catch (
const std::exception& e) {
517 impl_->last_error_msg = e.what();
518 return make_void_error(
519 -1, kcenon::pacs::compat::format(
"Connection failed: {}", e.what()),
524auto pacs_database_adapter::disconnect() -> VoidResult {
529 if (impl_->use_native_sqlite) {
530 if (impl_->in_transaction) {
531 auto rollback_result = rollback();
532 if (rollback_result.is_err()) {
537 if (impl_->native_sqlite_db !=
nullptr) {
538 sqlite3_close(impl_->native_sqlite_db);
539 impl_->native_sqlite_db =
nullptr;
542 impl_->use_native_sqlite =
false;
550 if (impl_->in_transaction) {
551 auto rollback_result = rollback();
552 if (rollback_result.is_err()) {
557 auto result = impl_->db->disconnect();
558 if (result.is_err()) {
559 impl_->last_error_msg = result.error().message;
560 return make_void_error(
562 kcenon::pacs::compat::format(
"Failed to disconnect: {}",
563 result.error().message),
570auto pacs_database_adapter::is_connected() const noexcept ->
bool {
572 ((impl_->use_native_sqlite && impl_->native_sqlite_db !=
nullptr) ||
573 (impl_->db && impl_->db->is_connected()));
576auto pacs_database_adapter::open_session() -> pacs_storage_session {
577 return pacs_storage_session(*
this);
580auto pacs_database_adapter::open_session() const -> pacs_storage_session {
581 return pacs_storage_session(
582 const_cast<pacs_database_adapter&
>(*
this));
585auto pacs_database_adapter::begin_unit_of_work()
586 -> Result<pacs_unit_of_work> {
587 auto result = begin_transaction_internal();
588 if (result.is_err()) {
589 return make_error<pacs_unit_of_work>(
590 result.error().code, result.error().message, result.error().module);
593 return Result<pacs_unit_of_work>::ok(pacs_unit_of_work(*
this,
true));
600auto pacs_database_adapter::create_query_builder() -> database::query_builder {
601 return database::query_builder(impl_->db_type);
608auto pacs_database_adapter::run_select(
const std::string& query)
609 -> Result<database_result> {
610 if (!is_connected()) {
611 return make_error<database_result>(
612 -1,
"Not connected to database",
"storage");
615 if (impl_->use_native_sqlite) {
616 auto result = select_native_sqlite(impl_->native_sqlite_db, query);
617 if (result.is_err()) {
618 impl_->last_error_msg = result.error().message;
619 return make_error<database_result>(
621 kcenon::pacs::compat::format(
"SELECT failed: {}", result.error().message),
628 auto result = impl_->db->select(query);
629 if (result.is_err()) {
630 impl_->last_error_msg = result.error().message;
631 return make_error<database_result>(
633 kcenon::pacs::compat::format(
"SELECT failed: {}", result.error().message),
638 convert_result(result.value()));
641auto pacs_database_adapter::run_insert(
const std::string& query)
642 -> Result<uint64_t> {
643 if (!is_connected()) {
644 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
647 if (impl_->use_native_sqlite) {
648 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
649 if (result.is_err()) {
650 impl_->last_error_msg = result.error().message;
651 return make_error<uint64_t>(
653 kcenon::pacs::compat::format(
"INSERT failed: {}", result.error().message),
657 impl_->last_rowid = sqlite3_last_insert_rowid(impl_->native_sqlite_db);
661 auto result = impl_->db->insert(query);
662 if (result.is_err()) {
663 impl_->last_error_msg = result.error().message;
664 return make_error<uint64_t>(
666 kcenon::pacs::compat::format(
"INSERT failed: {}", result.error().message),
671 if (impl_->db_type == database::database_types::sqlite) {
674 impl_->db->select(
"SELECT last_insert_rowid() as rowid");
675 if (rowid_result.is_ok() && !rowid_result.value().empty()) {
678 std::stoll(rowid_result.value()[0].at(
"rowid"));
686 static_cast<uint64_t
>(result.value()));
689auto pacs_database_adapter::run_update(
const std::string& query)
690 -> Result<uint64_t> {
691 if (!is_connected()) {
692 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
695 if (impl_->use_native_sqlite) {
696 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
697 if (result.is_err()) {
698 impl_->last_error_msg = result.error().message;
699 return make_error<uint64_t>(
701 kcenon::pacs::compat::format(
"UPDATE failed: {}", result.error().message),
708 auto result = impl_->db->update(query);
709 if (result.is_err()) {
710 impl_->last_error_msg = result.error().message;
711 return make_error<uint64_t>(
713 kcenon::pacs::compat::format(
"UPDATE failed: {}", result.error().message),
718 static_cast<uint64_t
>(result.value()));
721auto pacs_database_adapter::run_remove(
const std::string& query)
722 -> Result<uint64_t> {
723 if (!is_connected()) {
724 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
727 if (impl_->use_native_sqlite) {
728 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
729 if (result.is_err()) {
730 impl_->last_error_msg = result.error().message;
731 return make_error<uint64_t>(
733 kcenon::pacs::compat::format(
"DELETE failed: {}", result.error().message),
740 auto result = impl_->db->remove(query);
741 if (result.is_err()) {
742 impl_->last_error_msg = result.error().message;
743 return make_error<uint64_t>(
745 kcenon::pacs::compat::format(
"DELETE failed: {}", result.error().message),
750 static_cast<uint64_t
>(result.value()));
753auto pacs_database_adapter::run_execute(
const std::string& query)
755 if (!is_connected()) {
756 return make_void_error(-1,
"Not connected to database",
"storage");
759 if (impl_->use_native_sqlite) {
760 auto result = execute_native_sqlite(impl_->native_sqlite_db, query);
761 if (result.is_err()) {
762 impl_->last_error_msg = result.error().message;
763 return make_void_error(
765 kcenon::pacs::compat::format(
"Execute failed: {}", result.error().message),
772 auto result = impl_->db->execute(query);
773 if (result.is_err()) {
774 impl_->last_error_msg = result.error().message;
775 return make_void_error(
777 kcenon::pacs::compat::format(
"Execute failed: {}", result.error().message),
784auto pacs_database_adapter::select(
const std::string& query)
785 -> Result<database_result> {
786 return run_select(query);
789auto pacs_database_adapter::insert(
const std::string& query)
790 -> Result<uint64_t> {
791 return run_insert(query);
794auto pacs_database_adapter::update(
const std::string& query)
795 -> Result<uint64_t> {
796 return run_update(query);
799auto pacs_database_adapter::remove(
const std::string& query)
800 -> Result<uint64_t> {
801 return run_remove(query);
804auto pacs_database_adapter::execute(
const std::string& query) -> VoidResult {
805 return run_execute(query);
812auto pacs_database_adapter::begin_transaction_internal() -> VoidResult {
813 if (!is_connected()) {
814 return make_void_error(-1,
"Not connected to database",
"storage");
817 if (impl_->in_transaction) {
818 return make_void_error(-1,
"Transaction already in progress",
822 auto result = [&]() -> VoidResult {
823 if (impl_->use_native_sqlite) {
824 return execute_native_sqlite(impl_->native_sqlite_db,
825 "BEGIN TRANSACTION");
828 auto native_result = impl_->db->execute(
"BEGIN TRANSACTION");
829 if (native_result.is_err()) {
830 return make_void_error(native_result.error().code,
831 native_result.error().message,
832 native_result.error().module);
837 if (result.is_err()) {
838 impl_->last_error_msg = result.error().message;
839 return make_void_error(
841 kcenon::pacs::compat::format(
"BEGIN TRANSACTION failed: {}",
842 result.error().message),
846 impl_->in_transaction =
true;
850auto pacs_database_adapter::commit_internal() -> VoidResult {
851 if (!is_connected()) {
852 return make_void_error(-1,
"Not connected to database",
"storage");
855 if (!impl_->in_transaction) {
856 return make_void_error(-1,
"No transaction in progress",
"storage");
859 auto result = [&]() -> VoidResult {
860 if (impl_->use_native_sqlite) {
861 return execute_native_sqlite(impl_->native_sqlite_db,
"COMMIT");
864 auto native_result = impl_->db->execute(
"COMMIT");
865 if (native_result.is_err()) {
866 return make_void_error(native_result.error().code,
867 native_result.error().message,
868 native_result.error().module);
873 impl_->in_transaction =
false;
875 if (result.is_err()) {
876 impl_->last_error_msg = result.error().message;
877 return make_void_error(
879 kcenon::pacs::compat::format(
"COMMIT failed: {}", result.error().message),
886auto pacs_database_adapter::rollback_internal() -> VoidResult {
887 if (!is_connected()) {
891 if (!impl_->in_transaction) {
895 auto result = [&]() -> VoidResult {
896 if (impl_->use_native_sqlite) {
897 return execute_native_sqlite(impl_->native_sqlite_db,
"ROLLBACK");
900 auto native_result = impl_->db->execute(
"ROLLBACK");
901 if (native_result.is_err()) {
902 return make_void_error(native_result.error().code,
903 native_result.error().message,
904 native_result.error().module);
909 impl_->in_transaction =
false;
911 if (result.is_err()) {
912 impl_->last_error_msg = result.error().message;
913 return make_void_error(
915 kcenon::pacs::compat::format(
"ROLLBACK failed: {}",
916 result.error().message),
923auto pacs_database_adapter::begin_transaction() -> VoidResult {
924 return begin_transaction_internal();
927auto pacs_database_adapter::commit() -> VoidResult {
928 return commit_internal();
931auto pacs_database_adapter::rollback() -> VoidResult {
932 return rollback_internal();
935auto pacs_database_adapter::in_transaction() const noexcept ->
bool {
936 return impl_ && impl_->in_transaction;
943auto pacs_database_adapter::last_insert_rowid() const -> int64_t {
944 return impl_ ? impl_->last_rowid : 0;
947auto pacs_database_adapter::last_error() const -> std::
string {
948 return impl_ ? impl_->last_error_msg :
"";
955scoped_transaction::scoped_transaction(pacs_database_adapter& db) : db_(db) {
956 auto result = db_.begin_transaction();
957 active_ = result.is_ok();
960scoped_transaction::~scoped_transaction() {
961 if (active_ && !committed_) {
962 (void)db_.rollback();
966auto scoped_transaction::commit() -> VoidResult {
968 return make_void_error(-1,
"Transaction not active",
"storage");
972 return make_void_error(-1,
"Transaction already committed",
"storage");
975 auto result = db_.commit();
976 if (result.is_ok()) {
983void scoped_transaction::rollback() noexcept {
984 if (active_ && !committed_) {
985 (void)db_.rollback();
990auto scoped_transaction::is_active() const noexcept ->
bool {
991 return active_ && !committed_;
if(!color.empty()) style.color
@ move
C-MOVE move request/response.
@ other
Unknown or other category.
const atna_coded_value query
Query (110112)
kcenon::common::Result< T > Result
Result type alias for operations returning a value.
@ started
Procedure has been started (MPPS received)
Unified database adapter for PACS system.