17#ifdef PACS_WITH_DATABASE_SYSTEM
28using kcenon::common::make_error;
29using kcenon::common::ok;
35struct pacs_database_adapter::impl {
37 std::unique_ptr<database::integrated::unified_database_system> db;
40 sqlite3* native_sqlite_db{
nullptr};
43 bool use_native_sqlite{
false};
46 database::database_types db_type{database::database_types::sqlite};
49 std::string connection_string;
52 bool in_transaction{
false};
55 std::string last_error_msg;
58 int64_t last_rowid{0};
62 impl(database::database_types type, std::string conn_str)
63 : db_type(type), connection_string(std::
move(conn_str)) {}
75auto to_backend_type(database::database_types type)
76 -> database::integrated::backend_type {
78 case database::database_types::postgres:
79 return database::integrated::backend_type::postgres;
80 case database::database_types::sqlite:
81 return database::integrated::backend_type::sqlite;
82 case database::database_types::mongodb:
83 return database::integrated::backend_type::mongodb;
84 case database::database_types::redis:
85 return database::integrated::backend_type::redis;
87 return database::integrated::backend_type::sqlite;
91auto is_prefixed_sqlite_connection_string(std::string_view connection_string)
93 constexpr std::array<std::string_view, 3> prefixes = {
"dbname=",
"db=",
96 for (
const auto prefix : prefixes) {
97 if (connection_string.rfind(prefix, 0) == 0) {
105auto normalize_connection_string(database::database_types type,
106 std::string_view connection_string)
108 if (type != database::database_types::sqlite) {
109 return std::string(connection_string);
112 if (connection_string.empty()) {
113 return "dbname=:memory:";
116 if (is_prefixed_sqlite_connection_string(connection_string)) {
117 return std::string(connection_string);
120 return kcenon::pacs::compat::format(
"dbname={}", connection_string);
123auto extract_sqlite_connection_target(std::string_view connection_string)
125 constexpr std::array<std::string_view, 3> prefixes = {
"dbname=",
"db=",
128 for (
const auto prefix : prefixes) {
129 if (connection_string.rfind(prefix, 0) == 0) {
130 return std::string(connection_string.substr(prefix.size()));
134 return std::string(connection_string);
137auto should_use_native_sqlite(std::string_view connection_string) ->
bool {
138 const auto target = extract_sqlite_connection_target(connection_string);
139 return !target.empty() && target !=
":memory:";
146 const database::integrated::query_result& src) -> database_result {
147 database_result result;
148 result.affected_rows = src.affected_rows;
149 result.execution_time = src.execution_time;
151 result.rows.reserve(src.rows.size());
152 for (
const auto& src_row : src.rows) {
154 for (
const auto& [key, value] : src_row) {
157 result.rows.push_back(std::move(row));
166auto make_void_error(
int code,
const std::string& message,
167 const std::string& module) -> VoidResult {
168 return VoidResult(kcenon::common::error_info{
code, message, module});
171auto sqlite_error_message(sqlite3* db, std::string_view fallback)
174 if (
const auto* message = sqlite3_errmsg(db); message !=
nullptr) {
175 return std::string(message);
179 return std::string(fallback);
182auto execute_native_sqlite(sqlite3* db,
const std::string& query)
184 char* error_message =
nullptr;
186 sqlite3_exec(db,
query.c_str(),
nullptr,
nullptr, &error_message);
187 if (rc != SQLITE_OK) {
188 const std::string message =
189 error_message !=
nullptr ? std::string(error_message)
190 : sqlite_error_message(db,
"SQLite execution failed");
191 sqlite3_free(error_message);
192 return make_void_error(rc, message,
"storage");
198auto select_native_sqlite(sqlite3* db,
const std::string& query)
200 sqlite3_stmt* statement =
nullptr;
201 const auto started = std::chrono::steady_clock::now();
203 sqlite3_prepare_v2(db,
query.c_str(), -1, &statement,
nullptr);
204 if (rc != SQLITE_OK) {
205 return make_error<database_result>(
206 rc, sqlite_error_message(db,
"Failed to prepare SQLite query"),
210 database_result result;
212 const auto step_rc = sqlite3_step(statement);
213 if (step_rc == SQLITE_ROW) {
215 const auto column_count = sqlite3_column_count(statement);
216 for (
int i = 0; i < column_count; ++i) {
217 const auto* column_name = sqlite3_column_name(statement, i);
218 const auto* column_value = sqlite3_column_text(statement, i);
219 row[column_name !=
nullptr ? column_name :
""] =
220 column_value !=
nullptr
221 ?
reinterpret_cast<const char*
>(column_value)
224 result.rows.push_back(std::move(row));
228 sqlite3_finalize(statement);
229 if (step_rc != SQLITE_DONE) {
230 return make_error<database_result>(
232 sqlite_error_message(db,
"Failed to execute SQLite query"),
236 result.affected_rows = result.rows.size();
237 result.execution_time = std::chrono::duration_cast<std::chrono::microseconds>(
238 std::chrono::steady_clock::now() -
started);
239 return Result<database_result>::ok(std::move(result));
243auto mutate_native_sqlite(sqlite3* db,
const std::string& query)
245 auto result = execute_native_sqlite(db, query);
246 if (result.is_err()) {
247 return make_error<uint64_t>(result.error().code, result.error().message,
248 result.error().module);
251 return Result<uint64_t>::ok(
252 static_cast<uint64_t
>(sqlite3_changes(db)));
261pacs_storage_session::pacs_storage_session(
262 pacs_database_adapter& adapter) noexcept
263 : adapter_(&adapter) {}
265auto pacs_storage_session::create_query_builder() const
266 -> database::query_builder {
267 return adapter_->create_query_builder();
270auto pacs_storage_session::select(
const std::string& query)
272 return adapter_->run_select(query);
275auto pacs_storage_session::insert(
const std::string& query)
277 return adapter_->run_insert(query);
280auto pacs_storage_session::update(
const std::string& query)
282 return adapter_->run_update(query);
285auto pacs_storage_session::remove(
const std::string& query)
287 return adapter_->run_remove(query);
290auto pacs_storage_session::execute(
const std::string& query) -> VoidResult {
291 return adapter_->run_execute(query);
294auto pacs_storage_session::last_insert_rowid() const -> int64_t {
295 return adapter_->last_insert_rowid();
299 return adapter_->begin_unit_of_work();
306pacs_unit_of_work::pacs_unit_of_work(
307 pacs_database_adapter& adapter,
bool active) noexcept
308 : adapter_(&adapter), active_(active) {}
310pacs_unit_of_work::~pacs_unit_of_work() {
311 if (active_ && adapter_ !=
nullptr) {
312 (void)adapter_->rollback_internal();
316pacs_unit_of_work::pacs_unit_of_work(pacs_unit_of_work&& other) noexcept
317 : adapter_(
other.adapter_), active_(
other.active_) {
318 other.adapter_ =
nullptr;
319 other.active_ =
false;
322auto pacs_unit_of_work::operator=(pacs_unit_of_work&& other)
noexcept
323 -> pacs_unit_of_work& {
324 if (
this == &other) {
328 if (active_ && adapter_ !=
nullptr) {
329 (void)adapter_->rollback_internal();
332 adapter_ =
other.adapter_;
333 active_ =
other.active_;
334 other.adapter_ =
nullptr;
335 other.active_ =
false;
339auto pacs_unit_of_work::create_query_builder() const -> database::query_builder {
340 return adapter_->create_query_builder();
343auto pacs_unit_of_work::select(
const std::string& query)
345 return adapter_->run_select(query);
348auto pacs_unit_of_work::insert(
const std::string& query) ->
Result<uint64_t> {
349 return adapter_->run_insert(query);
352auto pacs_unit_of_work::update(
const std::string& query) ->
Result<uint64_t> {
353 return adapter_->run_update(query);
356auto pacs_unit_of_work::remove(
const std::string& query) ->
Result<uint64_t> {
357 return adapter_->run_remove(query);
360auto pacs_unit_of_work::execute(
const std::string& query) -> VoidResult {
361 return adapter_->run_execute(query);
364auto pacs_unit_of_work::last_insert_rowid() const -> int64_t {
365 return adapter_ !=
nullptr ? adapter_->last_insert_rowid() : 0;
368auto pacs_unit_of_work::commit() -> VoidResult {
369 if (!active_ || adapter_ ==
nullptr) {
370 return make_void_error(-1,
"Unit of work not active",
"storage");
373 auto result = adapter_->commit_internal();
374 if (result.is_ok()) {
380auto pacs_unit_of_work::rollback() -> VoidResult {
381 if (!active_ || adapter_ ==
nullptr) {
385 auto result = adapter_->rollback_internal();
386 if (result.is_ok()) {
392auto pacs_unit_of_work::is_active() const noexcept ->
bool {
400pacs_database_adapter::pacs_database_adapter(
401 const std::filesystem::path& db_path)
402 : impl_(std::make_unique<impl>(
403 database::database_types::sqlite,
404 normalize_connection_string(database::database_types::sqlite,
405 db_path.string()))) {}
407pacs_database_adapter::pacs_database_adapter(
408 database::database_types type,
const std::string& connection_string)
409 : impl_(std::make_unique<impl>(
410 type, normalize_connection_string(type, connection_string))) {}
412pacs_database_adapter::~pacs_database_adapter() {
417 if (impl_->use_native_sqlite && impl_->native_sqlite_db !=
nullptr) {
418 if (impl_->in_transaction) {
419 (void)execute_native_sqlite(impl_->native_sqlite_db,
"ROLLBACK");
421 sqlite3_close(impl_->native_sqlite_db);
422 impl_->native_sqlite_db =
nullptr;
426 if (impl_->db && impl_->db->is_connected()) {
427 if (impl_->in_transaction) {
428 (void)impl_->db->execute(
"ROLLBACK");
430 (void)impl_->db->disconnect();
434pacs_database_adapter::pacs_database_adapter(
435 pacs_database_adapter&&) noexcept = default;
437auto pacs_database_adapter::operator=(pacs_database_adapter&&) noexcept
438 -> pacs_database_adapter& = default;
444auto pacs_database_adapter::connect() -> VoidResult {
446 return make_void_error(-1,
"Adapter not initialized",
"storage");
449 if (impl_->db_type == database::database_types::sqlite &&
450 should_use_native_sqlite(impl_->connection_string)) {
452 extract_sqlite_connection_target(impl_->connection_string);
453 const auto rc = sqlite3_open_v2(
454 target.c_str(), &impl_->native_sqlite_db,
455 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
457 if (rc != SQLITE_OK) {
458 const auto message = sqlite_error_message(
459 impl_->native_sqlite_db,
"Failed to open SQLite database");
460 if (impl_->native_sqlite_db != nullptr) {
461 sqlite3_close(impl_->native_sqlite_db);
462 impl_->native_sqlite_db = nullptr;
464 impl_->last_error_msg = message;
465 return make_void_error(
466 rc, kcenon::pacs::compat::format(
"Failed to connect: {}", message),
470 (
void)sqlite3_busy_timeout(impl_->native_sqlite_db, 5000);
472 execute_native_sqlite(impl_->native_sqlite_db,
"PRAGMA foreign_keys = ON");
473 if (pragma_result.is_err()) {
474 impl_->last_error_msg = pragma_result.error().message;
475 sqlite3_close(impl_->native_sqlite_db);
476 impl_->native_sqlite_db = nullptr;
477 return pragma_result;
480 impl_->use_native_sqlite =
true;
481 impl_->last_error_msg.clear();
488 database::integrated::unified_database_system::create_builder()
489 .set_backend(to_backend_type(impl_->db_type))
490 .enable_logging(database::integrated::db_log_level::warning)
495 impl_->db->connect(to_backend_type(impl_->db_type),
496 impl_->connection_string);
498 if (result.is_err()) {
499 impl_->last_error_msg = result.error().message;
500 return make_void_error(
502 kcenon::pacs::compat::format(
"Failed to connect: {}",
503 result.error().message),
507 impl_->last_error_msg.clear();
509 }
catch (
const std::exception& e) {
510 impl_->last_error_msg = e.what();
511 return make_void_error(
512 -1, kcenon::pacs::compat::format(
"Connection failed: {}", e.what()),
517auto pacs_database_adapter::disconnect() -> VoidResult {
522 if (impl_->use_native_sqlite) {
523 if (impl_->in_transaction) {
524 auto rollback_result = rollback();
525 if (rollback_result.is_err()) {
530 if (impl_->native_sqlite_db !=
nullptr) {
531 sqlite3_close(impl_->native_sqlite_db);
532 impl_->native_sqlite_db =
nullptr;
535 impl_->use_native_sqlite =
false;
543 if (impl_->in_transaction) {
544 auto rollback_result = rollback();
545 if (rollback_result.is_err()) {
550 auto result = impl_->db->disconnect();
551 if (result.is_err()) {
552 impl_->last_error_msg = result.error().message;
553 return make_void_error(
555 kcenon::pacs::compat::format(
"Failed to disconnect: {}",
556 result.error().message),
563auto pacs_database_adapter::is_connected() const noexcept ->
bool {
565 ((impl_->use_native_sqlite && impl_->native_sqlite_db !=
nullptr) ||
566 (impl_->db && impl_->db->is_connected()));
569auto pacs_database_adapter::open_session() -> pacs_storage_session {
570 return pacs_storage_session(*
this);
573auto pacs_database_adapter::open_session() const -> pacs_storage_session {
574 return pacs_storage_session(
575 const_cast<pacs_database_adapter&
>(*
this));
578auto pacs_database_adapter::begin_unit_of_work()
579 -> Result<pacs_unit_of_work> {
580 auto result = begin_transaction_internal();
581 if (result.is_err()) {
582 return make_error<pacs_unit_of_work>(
583 result.error().code, result.error().message, result.error().module);
586 return Result<pacs_unit_of_work>::ok(pacs_unit_of_work(*
this,
true));
593auto pacs_database_adapter::create_query_builder() -> database::query_builder {
594 return database::query_builder(impl_->db_type);
601auto pacs_database_adapter::run_select(
const std::string& query)
602 -> Result<database_result> {
603 if (!is_connected()) {
604 return make_error<database_result>(
605 -1,
"Not connected to database",
"storage");
608 if (impl_->use_native_sqlite) {
609 auto result = select_native_sqlite(impl_->native_sqlite_db, query);
610 if (result.is_err()) {
611 impl_->last_error_msg = result.error().message;
612 return make_error<database_result>(
614 kcenon::pacs::compat::format(
"SELECT failed: {}", result.error().message),
621 auto result = impl_->db->select(query);
622 if (result.is_err()) {
623 impl_->last_error_msg = result.error().message;
624 return make_error<database_result>(
626 kcenon::pacs::compat::format(
"SELECT failed: {}", result.error().message),
631 convert_result(result.value()));
634auto pacs_database_adapter::run_insert(
const std::string& query)
635 -> Result<uint64_t> {
636 if (!is_connected()) {
637 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
640 if (impl_->use_native_sqlite) {
641 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
642 if (result.is_err()) {
643 impl_->last_error_msg = result.error().message;
644 return make_error<uint64_t>(
646 kcenon::pacs::compat::format(
"INSERT failed: {}", result.error().message),
650 impl_->last_rowid = sqlite3_last_insert_rowid(impl_->native_sqlite_db);
654 auto result = impl_->db->insert(query);
655 if (result.is_err()) {
656 impl_->last_error_msg = result.error().message;
657 return make_error<uint64_t>(
659 kcenon::pacs::compat::format(
"INSERT failed: {}", result.error().message),
664 if (impl_->db_type == database::database_types::sqlite) {
667 impl_->db->select(
"SELECT last_insert_rowid() as rowid");
668 if (rowid_result.is_ok() && !rowid_result.value().empty()) {
671 std::stoll(rowid_result.value()[0].at(
"rowid"));
679 static_cast<uint64_t
>(result.value()));
682auto pacs_database_adapter::run_update(
const std::string& query)
683 -> Result<uint64_t> {
684 if (!is_connected()) {
685 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
688 if (impl_->use_native_sqlite) {
689 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
690 if (result.is_err()) {
691 impl_->last_error_msg = result.error().message;
692 return make_error<uint64_t>(
694 kcenon::pacs::compat::format(
"UPDATE failed: {}", result.error().message),
701 auto result = impl_->db->update(query);
702 if (result.is_err()) {
703 impl_->last_error_msg = result.error().message;
704 return make_error<uint64_t>(
706 kcenon::pacs::compat::format(
"UPDATE failed: {}", result.error().message),
711 static_cast<uint64_t
>(result.value()));
714auto pacs_database_adapter::run_remove(
const std::string& query)
715 -> Result<uint64_t> {
716 if (!is_connected()) {
717 return make_error<uint64_t>(-1,
"Not connected to database",
"storage");
720 if (impl_->use_native_sqlite) {
721 auto result = mutate_native_sqlite(impl_->native_sqlite_db, query);
722 if (result.is_err()) {
723 impl_->last_error_msg = result.error().message;
724 return make_error<uint64_t>(
726 kcenon::pacs::compat::format(
"DELETE failed: {}", result.error().message),
733 auto result = impl_->db->remove(query);
734 if (result.is_err()) {
735 impl_->last_error_msg = result.error().message;
736 return make_error<uint64_t>(
738 kcenon::pacs::compat::format(
"DELETE failed: {}", result.error().message),
743 static_cast<uint64_t
>(result.value()));
746auto pacs_database_adapter::run_execute(
const std::string& query)
748 if (!is_connected()) {
749 return make_void_error(-1,
"Not connected to database",
"storage");
752 if (impl_->use_native_sqlite) {
753 auto result = execute_native_sqlite(impl_->native_sqlite_db, query);
754 if (result.is_err()) {
755 impl_->last_error_msg = result.error().message;
756 return make_void_error(
758 kcenon::pacs::compat::format(
"Execute failed: {}", result.error().message),
765 auto result = impl_->db->execute(query);
766 if (result.is_err()) {
767 impl_->last_error_msg = result.error().message;
768 return make_void_error(
770 kcenon::pacs::compat::format(
"Execute failed: {}", result.error().message),
777auto pacs_database_adapter::select(
const std::string& query)
778 -> Result<database_result> {
779 return run_select(query);
782auto pacs_database_adapter::insert(
const std::string& query)
783 -> Result<uint64_t> {
784 return run_insert(query);
787auto pacs_database_adapter::update(
const std::string& query)
788 -> Result<uint64_t> {
789 return run_update(query);
792auto pacs_database_adapter::remove(
const std::string& query)
793 -> Result<uint64_t> {
794 return run_remove(query);
797auto pacs_database_adapter::execute(
const std::string& query) -> VoidResult {
798 return run_execute(query);
805auto pacs_database_adapter::begin_transaction_internal() -> VoidResult {
806 if (!is_connected()) {
807 return make_void_error(-1,
"Not connected to database",
"storage");
810 if (impl_->in_transaction) {
811 return make_void_error(-1,
"Transaction already in progress",
815 auto result = [&]() -> VoidResult {
816 if (impl_->use_native_sqlite) {
817 return execute_native_sqlite(impl_->native_sqlite_db,
818 "BEGIN TRANSACTION");
821 auto native_result = impl_->db->execute(
"BEGIN TRANSACTION");
822 if (native_result.is_err()) {
823 return make_void_error(native_result.error().code,
824 native_result.error().message,
825 native_result.error().module);
830 if (result.is_err()) {
831 impl_->last_error_msg = result.error().message;
832 return make_void_error(
834 kcenon::pacs::compat::format(
"BEGIN TRANSACTION failed: {}",
835 result.error().message),
839 impl_->in_transaction =
true;
843auto pacs_database_adapter::commit_internal() -> VoidResult {
844 if (!is_connected()) {
845 return make_void_error(-1,
"Not connected to database",
"storage");
848 if (!impl_->in_transaction) {
849 return make_void_error(-1,
"No transaction in progress",
"storage");
852 auto result = [&]() -> VoidResult {
853 if (impl_->use_native_sqlite) {
854 return execute_native_sqlite(impl_->native_sqlite_db,
"COMMIT");
857 auto native_result = impl_->db->execute(
"COMMIT");
858 if (native_result.is_err()) {
859 return make_void_error(native_result.error().code,
860 native_result.error().message,
861 native_result.error().module);
866 impl_->in_transaction =
false;
868 if (result.is_err()) {
869 impl_->last_error_msg = result.error().message;
870 return make_void_error(
872 kcenon::pacs::compat::format(
"COMMIT failed: {}", result.error().message),
879auto pacs_database_adapter::rollback_internal() -> VoidResult {
880 if (!is_connected()) {
884 if (!impl_->in_transaction) {
888 auto result = [&]() -> VoidResult {
889 if (impl_->use_native_sqlite) {
890 return execute_native_sqlite(impl_->native_sqlite_db,
"ROLLBACK");
893 auto native_result = impl_->db->execute(
"ROLLBACK");
894 if (native_result.is_err()) {
895 return make_void_error(native_result.error().code,
896 native_result.error().message,
897 native_result.error().module);
902 impl_->in_transaction =
false;
904 if (result.is_err()) {
905 impl_->last_error_msg = result.error().message;
906 return make_void_error(
908 kcenon::pacs::compat::format(
"ROLLBACK failed: {}",
909 result.error().message),
916auto pacs_database_adapter::begin_transaction() -> VoidResult {
917 return begin_transaction_internal();
920auto pacs_database_adapter::commit() -> VoidResult {
921 return commit_internal();
924auto pacs_database_adapter::rollback() -> VoidResult {
925 return rollback_internal();
928auto pacs_database_adapter::in_transaction() const noexcept ->
bool {
929 return impl_ && impl_->in_transaction;
936auto pacs_database_adapter::last_insert_rowid() const -> int64_t {
937 return impl_ ? impl_->last_rowid : 0;
940auto pacs_database_adapter::last_error() const -> std::
string {
941 return impl_ ? impl_->last_error_msg :
"";
948scoped_transaction::scoped_transaction(pacs_database_adapter& db) : db_(db) {
949 auto result = db_.begin_transaction();
950 active_ = result.is_ok();
953scoped_transaction::~scoped_transaction() {
954 if (active_ && !committed_) {
955 (void)db_.rollback();
959auto scoped_transaction::commit() -> VoidResult {
961 return make_void_error(-1,
"Transaction not active",
"storage");
965 return make_void_error(-1,
"Transaction already committed",
"storage");
968 auto result = db_.commit();
969 if (result.is_ok()) {
976void scoped_transaction::rollback() noexcept {
977 if (active_ && !committed_) {
978 (void)db_.rollback();
983auto scoped_transaction::is_active() const noexcept ->
bool {
984 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.