27#ifdef PACS_WITH_DATABASE_SYSTEM
28#include <kcenon/database/query_builder.h>
45using kcenon::common::make_error;
46using kcenon::common::ok;
60auto parse_datetime(
const char* str)
61 -> std::chrono::system_clock::time_point {
62 if (!str || *str ==
'\0') {
63 return std::chrono::system_clock::now();
67 std::istringstream ss(str);
68 ss >> std::get_time(&tm,
"%Y-%m-%d %H:%M:%S");
71 return std::chrono::system_clock::now();
74 return std::chrono::system_clock::from_time_t(std::mktime(&tm));
80auto get_text(sqlite3_stmt* stmt,
int col) -> std::string {
82 reinterpret_cast<const char*
>(sqlite3_column_text(stmt, col));
83 return text ? std::string(
text) : std::string{};
86#ifdef PACS_WITH_DATABASE_SYSTEM
91 const std::map<std::string, database::core::database_value>& row,
92 const std::string& key) -> std::string {
93 auto it = row.find(key);
94 if (it == row.end()) {
97 if (std::holds_alternative<std::string>(it->second)) {
98 return std::get<std::string>(it->second);
107 const std::map<std::string, database::core::database_value>& row,
108 const std::string& key) -> int64_t {
109 auto it = row.find(key);
110 if (it == row.end()) {
113 if (std::holds_alternative<int64_t>(it->second)) {
114 return std::get<int64_t>(it->second);
116 if (std::holds_alternative<std::string>(it->second)) {
118 return std::stoll(std::get<std::string>(it->second));
126auto create_adapter_compatible_memory_path() -> std::string {
127 static std::atomic<uint64_t>
counter{0};
129 const auto unique_id =
130 std::chrono::steady_clock::now().time_since_epoch().count() +
131 static_cast<std::chrono::steady_clock::rep
>(
counter.fetch_add(1));
133 const auto file_name =
134 kcenon::pacs::compat::format(
"pacs_index_memory_{}.sqlite", unique_id);
135 return (std::filesystem::temp_directory_path() / file_name).string();
139void remove_database_sidecars(
const std::string& path) {
145 std::filesystem::remove(path, ec);
146 std::filesystem::remove(path +
"-wal", ec);
147 std::filesystem::remove(path +
"-shm", ec);
163 sqlite3* db =
nullptr;
165 std::string effective_path;
166#ifdef PACS_WITH_DATABASE_SYSTEM
167 bool remove_on_close =
false;
168 if (db_path ==
":memory:") {
171 effective_path = create_adapter_compatible_memory_path();
172 remove_on_close =
true;
174 effective_path = std::string(db_path);
177 effective_path = std::string(db_path);
180 auto rc = sqlite3_open_v2(effective_path.c_str(), &db,
181 SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_URI,
183 if (rc != SQLITE_OK) {
184 std::string error_msg =
185 db ? sqlite3_errmsg(db) :
"Failed to allocate memory";
189 return make_error<std::unique_ptr<index_database>>(
190 rc, kcenon::pacs::compat::format(
"Failed to open database: {}", error_msg),
195 rc = sqlite3_exec(db,
"PRAGMA foreign_keys = ON;",
nullptr,
nullptr,
197 if (rc != SQLITE_OK) {
199 return make_error<std::unique_ptr<index_database>>(
200 rc,
"Failed to enable foreign keys",
"storage");
208 if (config.wal_mode && effective_path !=
":memory:") {
209 rc = sqlite3_exec(db,
"PRAGMA journal_mode = WAL;",
nullptr,
nullptr,
211 if (rc != SQLITE_OK) {
213 return make_error<std::unique_ptr<index_database>>(
214 rc,
"Failed to enable WAL mode",
"storage");
220 kcenon::pacs::compat::format(
"PRAGMA cache_size = -{};", config.cache_size_mb * 1024);
221 rc = sqlite3_exec(db, cache_sql.c_str(),
nullptr,
nullptr,
nullptr);
222 if (rc != SQLITE_OK) {
224 return make_error<std::unique_ptr<index_database>>(
225 rc,
"Failed to set cache size",
"storage");
230 if (config.mmap_enabled && effective_path !=
":memory:") {
231 auto mmap_sql = kcenon::pacs::compat::format(
"PRAGMA mmap_size = {};", config.mmap_size);
232 rc = sqlite3_exec(db, mmap_sql.c_str(),
nullptr,
nullptr,
nullptr);
233 if (rc != SQLITE_OK) {
239 rc = sqlite3_exec(db,
"PRAGMA synchronous = NORMAL;",
nullptr,
nullptr,
241 if (rc != SQLITE_OK) {
246 auto instance = std::unique_ptr<index_database>(
248#ifdef PACS_WITH_DATABASE_SYSTEM
249 instance->remove_on_close_ = remove_on_close;
255 return make_error<std::unique_ptr<index_database>>(
257 kcenon::pacs::compat::format(
"Migration failed: {}",
262#ifdef PACS_WITH_DATABASE_SYSTEM
265 auto db_init_result = instance->initialize_database_system();
266 if (db_init_result.is_err()) {
272 auto repository_result = instance->initialize_repositories();
273 if (repository_result.is_err()) {
274 return make_error<std::unique_ptr<index_database>>(
275 repository_result.error().
code,
276 kcenon::pacs::compat::format(
"Repository initialization failed: {}",
277 repository_result.error().message),
285 : db_(db), path_(std::move(path)) {}
296#ifdef PACS_WITH_DATABASE_SYSTEM
298 (void)db_adapter_->disconnect();
307 remove_database_sidecars(
path_);
311#ifdef PACS_WITH_DATABASE_SYSTEM
312auto index_database::initialize_database_system() -> VoidResult {
313 return initialize_database_adapter();
316auto index_database::initialize_database_adapter() -> VoidResult {
317 db_adapter_ = std::make_shared<pacs_database_adapter>(
318 std::filesystem::path(path_));
320 auto connect_result = db_adapter_->connect();
321 if (connect_result.is_err()) {
323 return make_error<std::monostate>(
325 kcenon::pacs::compat::format(
"Failed to connect adapter: {}",
326 connect_result.error().message),
333auto index_database::parse_patient_from_adapter_row(
const database_row& row)
const
335 patient_record record;
337 auto get_str = [&row](
const std::string& key) -> std::string {
338 auto it = row.find(key);
339 return (it != row.end()) ? it->second : std::string{};
342 auto get_int64 = [&row](
const std::string& key) -> int64_t {
343 auto it = row.find(key);
344 if (it != row.end() && !it->second.empty()) {
346 return std::stoll(it->second);
354 record.pk = get_int64(
"patient_pk");
355 record.patient_id = get_str(
"patient_id");
356 record.patient_name = get_str(
"patient_name");
357 record.birth_date = get_str(
"birth_date");
358 record.sex = get_str(
"sex");
359 record.other_ids = get_str(
"other_ids");
360 record.ethnic_group = get_str(
"ethnic_group");
361 record.comments = get_str(
"comments");
363 auto created_at_str = get_str(
"created_at");
364 if (!created_at_str.empty()) {
365 record.created_at = parse_datetime(created_at_str.c_str());
368 auto updated_at_str = get_str(
"updated_at");
369 if (!updated_at_str.empty()) {
370 record.updated_at = parse_datetime(updated_at_str.c_str());
376auto index_database::parse_study_from_adapter_row(
const database_row& row)
const
380 auto get_str = [&row](
const std::string& key) -> std::string {
381 auto it = row.find(key);
382 return (it != row.end()) ? it->second : std::string{};
385 auto get_int64 = [&row](
const std::string& key) -> int64_t {
386 auto it = row.find(key);
387 if (it != row.end() && !it->second.empty()) {
389 return std::stoll(it->second);
397 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
398 auto it = row.find(key);
399 if (it != row.end() && !it->second.empty()) {
401 return std::stoi(it->second);
409 record.pk = get_int64(
"study_pk");
410 record.patient_pk = get_int64(
"patient_pk");
411 record.study_uid = get_str(
"study_uid");
412 record.study_id = get_str(
"study_id");
413 record.study_date = get_str(
"study_date");
414 record.study_time = get_str(
"study_time");
415 record.accession_number = get_str(
"accession_number");
416 record.referring_physician = get_str(
"referring_physician");
417 record.study_description = get_str(
"study_description");
418 record.modalities_in_study = get_str(
"modalities_in_study");
419 record.num_instances = get_optional_int(
"num_instances").value_or(0);
420 record.num_series = get_optional_int(
"num_series").value_or(0);
422 auto created_at_str = get_str(
"created_at");
423 if (!created_at_str.empty()) {
424 record.created_at = parse_datetime(created_at_str.c_str());
427 auto updated_at_str = get_str(
"updated_at");
428 if (!updated_at_str.empty()) {
429 record.updated_at = parse_datetime(updated_at_str.c_str());
435auto index_database::parse_series_from_adapter_row(
const database_row& row)
const
439 auto get_str = [&row](
const std::string& key) -> std::string {
440 auto it = row.find(key);
441 return (it != row.end()) ? it->second : std::string{};
444 auto get_int64 = [&row](
const std::string& key) -> int64_t {
445 auto it = row.find(key);
446 if (it != row.end() && !it->second.empty()) {
448 return std::stoll(it->second);
456 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
457 auto it = row.find(key);
458 if (it != row.end() && !it->second.empty()) {
460 return std::stoi(it->second);
468 record.pk = get_int64(
"series_pk");
469 record.study_pk = get_int64(
"study_pk");
470 record.series_uid = get_str(
"series_uid");
471 record.modality = get_str(
"modality");
472 record.series_number = get_optional_int(
"series_number");
473 record.series_description = get_str(
"series_description");
474 record.body_part_examined = get_str(
"body_part_examined");
475 record.station_name = get_str(
"station_name");
476 record.num_instances = get_optional_int(
"num_instances").value_or(0);
478 auto created_at_str = get_str(
"created_at");
479 if (!created_at_str.empty()) {
480 record.created_at = parse_datetime(created_at_str.c_str());
483 auto updated_at_str = get_str(
"updated_at");
484 if (!updated_at_str.empty()) {
485 record.updated_at = parse_datetime(updated_at_str.c_str());
491auto index_database::parse_instance_from_adapter_row(
const database_row& row)
const
495 auto get_str = [&row](
const std::string& key) -> std::string {
496 auto it = row.find(key);
497 return (it != row.end()) ? it->second : std::string{};
500 auto get_int64 = [&row](
const std::string& key) -> int64_t {
501 auto it = row.find(key);
502 if (it != row.end() && !it->second.empty()) {
504 return std::stoll(it->second);
512 auto get_optional_int = [&row](
const std::string& key) -> std::optional<int> {
513 auto it = row.find(key);
514 if (it != row.end() && !it->second.empty()) {
516 return std::stoi(it->second);
524 record.pk = get_int64(
"instance_pk");
525 record.series_pk = get_int64(
"series_pk");
526 record.sop_uid = get_str(
"sop_uid");
527 record.sop_class_uid = get_str(
"sop_class_uid");
528 record.instance_number = get_optional_int(
"instance_number");
529 record.transfer_syntax = get_str(
"transfer_syntax");
530 record.content_date = get_str(
"content_date");
531 record.content_time = get_str(
"content_time");
532 record.rows = get_optional_int(
"rows");
533 record.columns = get_optional_int(
"columns");
534 record.bits_allocated = get_optional_int(
"bits_allocated");
535 record.number_of_frames = get_optional_int(
"number_of_frames");
536 record.file_path = get_str(
"file_path");
537 record.file_size = get_int64(
"file_size");
538 record.file_hash = get_str(
"file_hash");
540 auto created_at_str = get_str(
"created_at");
541 if (!created_at_str.empty()) {
542 record.created_at = parse_datetime(created_at_str.c_str());
548auto index_database::parse_mpps_from_adapter_row(
const database_row& row)
const
552 auto get_str = [&row](
const std::string& key) -> std::string {
553 auto it = row.find(key);
554 return (it != row.end()) ? it->second : std::string{};
557 auto get_int64 = [&row](
const std::string& key) -> int64_t {
558 auto it = row.find(key);
559 if (it != row.end() && !it->second.empty()) {
561 return std::stoll(it->second);
569 record.pk = get_int64(
"mpps_pk");
570 record.mpps_uid = get_str(
"mpps_uid");
571 record.status = get_str(
"status");
572 record.start_datetime = get_str(
"start_datetime");
573 record.end_datetime = get_str(
"end_datetime");
574 record.station_ae = get_str(
"station_ae");
575 record.station_name = get_str(
"station_name");
576 record.modality = get_str(
"modality");
577 record.study_uid = get_str(
"study_uid");
578 record.accession_no = get_str(
"accession_no");
579 record.scheduled_step_id = get_str(
"scheduled_step_id");
580 record.requested_proc_id = get_str(
"requested_proc_id");
581 record.performed_series = get_str(
"performed_series");
583 auto created_at_str = get_str(
"created_at");
584 if (!created_at_str.empty()) {
585 record.created_at = parse_datetime(created_at_str.c_str());
588 auto updated_at_str = get_str(
"updated_at");
589 if (!updated_at_str.empty()) {
590 record.updated_at = parse_datetime(updated_at_str.c_str());
596auto index_database::parse_worklist_from_adapter_row(
const database_row& row)
const
600 auto get_str = [&row](
const std::string& key) -> std::string {
601 auto it = row.find(key);
602 return (it != row.end()) ? it->second : std::string{};
605 auto get_int64 = [&row](
const std::string& key) -> int64_t {
606 auto it = row.find(key);
607 if (it != row.end() && !it->second.empty()) {
609 return std::stoll(it->second);
617 item.pk = get_int64(
"worklist_pk");
618 item.step_id = get_str(
"step_id");
619 item.step_status = get_str(
"step_status");
620 item.patient_id = get_str(
"patient_id");
621 item.patient_name = get_str(
"patient_name");
622 item.birth_date = get_str(
"birth_date");
623 item.sex = get_str(
"sex");
624 item.accession_no = get_str(
"accession_no");
625 item.requested_proc_id = get_str(
"requested_proc_id");
626 item.study_uid = get_str(
"study_uid");
627 item.scheduled_datetime = get_str(
"scheduled_datetime");
628 item.station_ae = get_str(
"station_ae");
629 item.station_name = get_str(
"station_name");
630 item.modality = get_str(
"modality");
631 item.procedure_desc = get_str(
"procedure_desc");
632 item.protocol_code = get_str(
"protocol_code");
633 item.referring_phys = get_str(
"referring_phys");
634 item.referring_phys_id = get_str(
"referring_phys_id");
636 auto created_at_str = get_str(
"created_at");
637 if (!created_at_str.empty()) {
638 item.created_at = parse_datetime(created_at_str.c_str());
641 auto updated_at_str = get_str(
"updated_at");
642 if (!updated_at_str.empty()) {
643 item.updated_at = parse_datetime(updated_at_str.c_str());
651#ifdef PACS_WITH_DATABASE_SYSTEM
652 if (!db_adapter_ || !db_adapter_->is_connected()) {
653 return make_error<std::monostate>(
655 "PACS database adapter is not connected",
659 patient_repository_ = std::make_shared<patient_repository>(db_adapter_);
660 study_repository_ = std::make_shared<study_repository>(db_adapter_);
661 series_repository_ = std::make_shared<series_repository>(db_adapter_);
662 instance_repository_ = std::make_shared<instance_repository>(db_adapter_);
663 mpps_repository_ = std::make_shared<mpps_repository>(db_adapter_);
664 worklist_repository_ = std::make_shared<worklist_repository>(db_adapter_);
665 ups_repository_ = std::make_shared<ups_repository>(db_adapter_);
666 audit_repository_ = std::make_shared<audit_repository>(db_adapter_);
668 if (db_ ==
nullptr) {
669 return make_error<std::monostate>(
671 "SQLite database handle is not available",
675 patient_repository_ = std::make_shared<patient_repository>(db_);
676 study_repository_ = std::make_shared<study_repository>(db_);
677 series_repository_ = std::make_shared<series_repository>(db_);
678 instance_repository_ = std::make_shared<instance_repository>(db_);
679 mpps_repository_ = std::make_shared<mpps_repository>(db_);
680 worklist_repository_ = std::make_shared<worklist_repository>(db_);
681 ups_repository_ = std::make_shared<ups_repository>(db_);
682 audit_repository_ = std::make_shared<audit_repository>(db_);
690 path_(std::move(other.path_)),
691 remove_on_close_(other.remove_on_close_),
692 patient_repository_(std::move(other.patient_repository_)),
693 study_repository_(std::move(other.study_repository_)),
694 series_repository_(std::move(other.series_repository_)),
695 instance_repository_(std::move(other.instance_repository_)),
696 mpps_repository_(std::move(other.mpps_repository_)),
697 worklist_repository_(std::move(other.worklist_repository_)),
698 ups_repository_(std::move(other.ups_repository_)),
699 audit_repository_(std::move(other.audit_repository_)) {
700#ifdef PACS_WITH_DATABASE_SYSTEM
701 db_adapter_ = std::move(other.db_adapter_);
704 other.remove_on_close_ =
false;
709 if (
this != &other) {
710 patient_repository_.reset();
711 study_repository_.reset();
712 series_repository_.reset();
713 instance_repository_.reset();
714 mpps_repository_.reset();
715 worklist_repository_.reset();
716 ups_repository_.reset();
717 audit_repository_.reset();
718#ifdef PACS_WITH_DATABASE_SYSTEM
720 (void)db_adapter_->disconnect();
722 db_adapter_ = std::move(other.db_adapter_);
727 if (remove_on_close_) {
728 remove_database_sidecars(path_);
731 path_ = std::move(other.path_);
732 remove_on_close_ = other.remove_on_close_;
733 patient_repository_ = std::move(other.patient_repository_);
734 study_repository_ = std::move(other.study_repository_);
735 series_repository_ = std::move(other.series_repository_);
736 instance_repository_ = std::move(other.instance_repository_);
737 mpps_repository_ = std::move(other.mpps_repository_);
738 worklist_repository_ = std::move(other.worklist_repository_);
739 ups_repository_ = std::move(other.ups_repository_);
740 audit_repository_ = std::move(other.audit_repository_);
742 other.remove_on_close_ =
false;
752 std::string_view patient_name,
753 std::string_view birth_date,
755 return patient_repository_->upsert_patient(patient_id, patient_name,
761 return patient_repository_->upsert_patient(record);
765 -> std::optional<patient_record> {
766 return patient_repository_->find_patient(patient_id);
770 -> std::optional<patient_record> {
771 return patient_repository_->find_patient_by_pk(pk);
776 return patient_repository_->search_patients(query);
780 return patient_repository_->delete_patient(patient_id);
804 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
807 record.pk = sqlite3_column_int64(stmt, 0);
808 record.patient_id = get_text(stmt, 1);
809 record.patient_name = get_text(stmt, 2);
810 record.birth_date = get_text(stmt, 3);
811 record.sex = get_text(stmt, 4);
812 record.other_ids = get_text(stmt, 5);
813 record.ethnic_group = get_text(stmt, 6);
814 record.comments = get_text(stmt, 7);
816 auto created_str = get_text(stmt, 8);
817 record.created_at = parse_datetime(created_str.c_str());
819 auto updated_str = get_text(stmt, 9);
820 record.updated_at = parse_datetime(updated_str.c_str());
827 result.reserve(pattern.size());
829 for (
char c : pattern) {
832 }
else if (c ==
'?') {
834 }
else if (c ==
'%' || c ==
'_') {
851 std::string_view study_uid,
852 std::string_view study_id,
853 std::string_view study_date,
854 std::string_view study_time,
855 std::string_view accession_number,
856 std::string_view referring_physician,
857 std::string_view study_description)
859 return study_repository_->upsert_study(
860 patient_pk, study_uid, study_id, study_date, study_time,
861 accession_number, referring_physician, study_description);
866 return study_repository_->upsert_study(record);
870 -> std::optional<study_record> {
871 return study_repository_->find_study(study_uid);
875 -> std::optional<study_record> {
876 return study_repository_->find_study_by_pk(pk);
882 query.patient_id = std::string(patient_id);
883 return study_repository_->search_studies(query);
888 return study_repository_->search_studies(query);
892 return study_repository_->delete_study(study_uid);
900 auto patient = patient_repository_->find_patient(patient_id);
901 if (!patient.has_value()) {
902 return ok(
static_cast<size_t>(0));
904 return study_repository_->study_count_for_patient(patient->pk);
909 return study_repository_->update_modalities_in_study(study_pk);
913 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
916 record.pk = sqlite3_column_int64(stmt, 0);
917 record.patient_pk = sqlite3_column_int64(stmt, 1);
918 record.study_uid = get_text(stmt, 2);
919 record.study_id = get_text(stmt, 3);
920 record.study_date = get_text(stmt, 4);
921 record.study_time = get_text(stmt, 5);
922 record.accession_number = get_text(stmt, 6);
923 record.referring_physician = get_text(stmt, 7);
924 record.study_description = get_text(stmt, 8);
925 record.modalities_in_study = get_text(stmt, 9);
926 record.num_series = sqlite3_column_int(stmt, 10);
927 record.num_instances = sqlite3_column_int(stmt, 11);
929 auto created_str = get_text(stmt, 12);
930 record.created_at = parse_datetime(created_str.c_str());
932 auto updated_str = get_text(stmt, 13);
933 record.updated_at = parse_datetime(updated_str.c_str());
943 std::string_view series_uid,
944 std::string_view modality,
945 std::optional<int> series_number,
946 std::string_view series_description,
947 std::string_view body_part_examined,
948 std::string_view station_name)
951 record.study_pk = study_pk;
952 record.series_uid = std::string(series_uid);
953 record.modality = std::string(modality);
954 record.series_number = series_number;
955 record.series_description = std::string(series_description);
956 record.body_part_examined = std::string(body_part_examined);
957 record.station_name = std::string(station_name);
958 return upsert_series(record);
963 auto existing = find_series(record.series_uid);
964 auto result = series_repository_->upsert_series(record);
965 if (result.is_err()) {
969 if (existing.has_value() && existing->study_pk > 0 &&
970 existing->study_pk != record.study_pk) {
971 (void)update_modalities_in_study(existing->study_pk);
973 if (record.study_pk > 0) {
974 (void)update_modalities_in_study(record.study_pk);
981 -> std::optional<series_record> {
982 return series_repository_->find_series(series_uid);
986 -> std::optional<series_record> {
987 return series_repository_->find_series_by_pk(pk);
992 return series_repository_->list_series(study_uid);
997 return series_repository_->search_series(query);
1001 auto existing = find_series(series_uid);
1002 auto result = series_repository_->delete_series(series_uid);
1003 if (result.is_ok() && existing.has_value() && existing->study_pk > 0) {
1004 (void)update_modalities_in_study(existing->study_pk);
1014 return series_repository_->series_count(study_uid);
1018 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1021 record.pk = sqlite3_column_int64(stmt, 0);
1022 record.study_pk = sqlite3_column_int64(stmt, 1);
1023 record.series_uid = get_text(stmt, 2);
1024 record.modality = get_text(stmt, 3);
1027 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1028 record.series_number = sqlite3_column_int(stmt, 4);
1031 record.series_description = get_text(stmt, 5);
1032 record.body_part_examined = get_text(stmt, 6);
1033 record.station_name = get_text(stmt, 7);
1034 record.num_instances = sqlite3_column_int(stmt, 8);
1036 auto created_str = get_text(stmt, 9);
1037 record.created_at = parse_datetime(created_str.c_str());
1039 auto updated_str = get_text(stmt, 10);
1040 record.updated_at = parse_datetime(updated_str.c_str());
1050 std::string_view sop_uid,
1051 std::string_view sop_class_uid,
1052 std::string_view file_path,
1054 std::string_view transfer_syntax,
1055 std::optional<int> instance_number)
1057 return instance_repository_->upsert_instance(
1058 series_pk, sop_uid, sop_class_uid, file_path, file_size,
1059 transfer_syntax, instance_number);
1064 return instance_repository_->upsert_instance(record);
1068 -> std::optional<instance_record> {
1069 return instance_repository_->find_instance(sop_uid);
1073 -> std::optional<instance_record> {
1074 return instance_repository_->find_instance_by_pk(pk);
1079 return instance_repository_->list_instances(series_uid);
1084 return instance_repository_->search_instances(query);
1088 return instance_repository_->delete_instance(sop_uid);
1097 return instance_repository_->instance_count(series_uid);
1102 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1105 record.pk = sqlite3_column_int64(stmt, 0);
1106 record.series_pk = sqlite3_column_int64(stmt, 1);
1107 record.sop_uid = get_text(stmt, 2);
1108 record.sop_class_uid = get_text(stmt, 3);
1111 if (sqlite3_column_type(stmt, 4) != SQLITE_NULL) {
1112 record.instance_number = sqlite3_column_int(stmt, 4);
1115 record.transfer_syntax = get_text(stmt, 5);
1116 record.content_date = get_text(stmt, 6);
1117 record.content_time = get_text(stmt, 7);
1120 if (sqlite3_column_type(stmt, 8) != SQLITE_NULL) {
1121 record.rows = sqlite3_column_int(stmt, 8);
1124 if (sqlite3_column_type(stmt, 9) != SQLITE_NULL) {
1125 record.columns = sqlite3_column_int(stmt, 9);
1128 if (sqlite3_column_type(stmt, 10) != SQLITE_NULL) {
1129 record.bits_allocated = sqlite3_column_int(stmt, 10);
1132 if (sqlite3_column_type(stmt, 11) != SQLITE_NULL) {
1133 record.number_of_frames = sqlite3_column_int(stmt, 11);
1136 record.file_path = get_text(stmt, 12);
1137 record.file_size = sqlite3_column_int64(stmt, 13);
1138 record.file_hash = get_text(stmt, 14);
1140 auto created_str = get_text(stmt, 15);
1141 record.created_at = parse_datetime(created_str.c_str());
1152 return instance_repository_->get_file_path(sop_instance_uid);
1157 return instance_repository_->get_study_files(study_instance_uid);
1162 return instance_repository_->get_series_files(series_instance_uid);
1170#ifdef PACS_WITH_DATABASE_SYSTEM
1172 if (db_adapter_ && db_adapter_->is_connected()) {
1173 return db_adapter_->execute(
"VACUUM;");
1177 auto rc = sqlite3_exec(db_,
"VACUUM;",
nullptr,
nullptr,
nullptr);
1178 if (rc != SQLITE_OK) {
1179 return make_error<std::monostate>(
1180 rc, kcenon::pacs::compat::format(
"VACUUM failed: {}", sqlite3_errmsg(db_)),
1187#ifdef PACS_WITH_DATABASE_SYSTEM
1189 if (db_adapter_ && db_adapter_->is_connected()) {
1190 return db_adapter_->execute(
"ANALYZE;");
1194 auto rc = sqlite3_exec(db_,
"ANALYZE;",
nullptr,
nullptr,
nullptr);
1195 if (rc != SQLITE_OK) {
1196 return make_error<std::monostate>(
1197 rc, kcenon::pacs::compat::format(
"ANALYZE failed: {}", sqlite3_errmsg(db_)),
1204#ifdef PACS_WITH_DATABASE_SYSTEM
1206 if (db_adapter_ && db_adapter_->is_connected()) {
1207 auto result = db_adapter_->select(
"PRAGMA integrity_check;");
1208 if (result.is_err()) {
1209 return make_error<std::monostate>(
1210 result.error().code, result.error().message,
"storage");
1213 for (
const auto& row : result.value()) {
1214 auto it = row.find(
"integrity_check");
1215 if (it != row.end() && it->second !=
"ok") {
1216 return make_error<std::monostate>(
1217 -1, kcenon::pacs::compat::format(
"Integrity check failed: {}", it->second),
1225 const char* sql =
"PRAGMA integrity_check;";
1227 sqlite3_stmt* stmt =
nullptr;
1228 auto rc = sqlite3_prepare_v2(
db_, sql, -1, &stmt,
nullptr);
1229 if (rc != SQLITE_OK) {
1230 return make_error<std::monostate>(
1232 kcenon::pacs::compat::format(
"Failed to prepare integrity check: {}",
1233 sqlite3_errmsg(
db_)),
1238 while (sqlite3_step(stmt) == SQLITE_ROW) {
1239 result = get_text(stmt, 0);
1240 if (result !=
"ok") {
1241 sqlite3_finalize(stmt);
1242 return make_error<std::monostate>(
1243 -1, kcenon::pacs::compat::format(
"Integrity check failed: {}", result),
1248 sqlite3_finalize(stmt);
1254 truncate ?
"PRAGMA wal_checkpoint(TRUNCATE);"
1255 :
"PRAGMA wal_checkpoint(PASSIVE);";
1257#ifdef PACS_WITH_DATABASE_SYSTEM
1259 if (db_adapter_ && db_adapter_->is_connected()) {
1260 return db_adapter_->execute(sql);
1264 auto rc = sqlite3_exec(db_, sql,
nullptr,
nullptr,
nullptr);
1265 if (rc != SQLITE_OK) {
1266 return make_error<std::monostate>(
1267 rc, kcenon::pacs::compat::format(
"Checkpoint failed: {}", sqlite3_errmsg(db_)),
1277#ifdef PACS_WITH_DATABASE_SYSTEM
1278auto index_database::db_adapter() const noexcept
1279 -> std::shared_ptr<pacs_database_adapter> {
1292 if (patient_count_result.is_err()) {
1298 if (study_count_result.is_err()) {
1304 if (series_count_result.is_err()) {
1310 if (instance_count_result.is_err()) {
1316 const char* file_size_sql =
1317 "SELECT COALESCE(SUM(file_size), 0) AS total_size FROM instances;";
1319 sqlite3_stmt* stmt =
nullptr;
1320 auto rc = sqlite3_prepare_v2(
db_, file_size_sql, -1, &stmt,
nullptr);
1321 if (rc != SQLITE_OK) {
1324 kcenon::pacs::compat::format(
"Failed to prepare query: {}",
1325 sqlite3_errmsg(
db_)));
1328 if (sqlite3_step(stmt) == SQLITE_ROW) {
1331 sqlite3_finalize(stmt);
1334 if (
path_ !=
":memory:") {
1336 auto size = std::filesystem::file_size(
path_, ec);
1342 return ok(std::move(stats));
1350 std::string_view station_ae,
1351 std::string_view modality,
1352 std::string_view study_uid,
1353 std::string_view accession_no,
1354 std::string_view start_datetime)
1356 return mpps_repository_->create_mpps(mpps_uid, station_ae, modality,
1357 study_uid, accession_no,
1362 return mpps_repository_->create_mpps(record);
1366 std::string_view new_status,
1367 std::string_view end_datetime,
1368 std::string_view performed_series)
1370 return mpps_repository_->update_mpps(mpps_uid, new_status, end_datetime,
1375 return mpps_repository_->update_mpps(record);
1379 -> std::optional<mpps_record> {
1380 return mpps_repository_->find_mpps(mpps_uid);
1384 -> std::optional<mpps_record> {
1385 return mpps_repository_->find_mpps_by_pk(pk);
1390 return mpps_repository_->list_active_mpps(station_ae);
1395 return mpps_repository_->find_mpps_by_study(study_uid);
1400 return mpps_repository_->search_mpps(query);
1404 return mpps_repository_->delete_mpps(mpps_uid);
1412 return mpps_repository_->mpps_count(status);
1416 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1419 record.pk = sqlite3_column_int64(stmt, 0);
1420 record.mpps_uid = get_text(stmt, 1);
1421 record.status = get_text(stmt, 2);
1422 record.start_datetime = get_text(stmt, 3);
1423 record.end_datetime = get_text(stmt, 4);
1424 record.station_ae = get_text(stmt, 5);
1425 record.station_name = get_text(stmt, 6);
1426 record.modality = get_text(stmt, 7);
1427 record.study_uid = get_text(stmt, 8);
1428 record.accession_no = get_text(stmt, 9);
1429 record.scheduled_step_id = get_text(stmt, 10);
1430 record.requested_proc_id = get_text(stmt, 11);
1431 record.performed_series = get_text(stmt, 12);
1433 auto created_str = get_text(stmt, 13);
1434 record.created_at = parse_datetime(created_str.c_str());
1436 auto updated_str = get_text(stmt, 14);
1437 record.updated_at = parse_datetime(updated_str.c_str());
1442#ifdef PACS_WITH_DATABASE_SYSTEM
1443auto index_database::parse_mpps_from_row(
1444 const std::map<std::string, database::core::database_value>& row)
const
1448 record.pk = get_int64_value(row,
"mpps_pk");
1449 record.mpps_uid = get_string_value(row,
"mpps_uid");
1450 record.status = get_string_value(row,
"status");
1451 record.start_datetime = get_string_value(row,
"start_datetime");
1452 record.end_datetime = get_string_value(row,
"end_datetime");
1453 record.station_ae = get_string_value(row,
"station_ae");
1454 record.station_name = get_string_value(row,
"station_name");
1455 record.modality = get_string_value(row,
"modality");
1456 record.study_uid = get_string_value(row,
"study_uid");
1457 record.accession_no = get_string_value(row,
"accession_no");
1458 record.scheduled_step_id = get_string_value(row,
"scheduled_step_id");
1459 record.requested_proc_id = get_string_value(row,
"requested_proc_id");
1460 record.performed_series = get_string_value(row,
"performed_series");
1462 auto created_str = get_string_value(row,
"created_at");
1463 record.created_at = parse_datetime(created_str.c_str());
1465 auto updated_str = get_string_value(row,
"updated_at");
1466 record.updated_at = parse_datetime(updated_str.c_str());
1471auto index_database::parse_worklist_from_row(
1472 const std::map<std::string, database::core::database_value>& row)
const
1476 item.pk = get_int64_value(row,
"worklist_pk");
1477 item.step_id = get_string_value(row,
"step_id");
1478 item.step_status = get_string_value(row,
"step_status");
1479 item.patient_id = get_string_value(row,
"patient_id");
1480 item.patient_name = get_string_value(row,
"patient_name");
1481 item.birth_date = get_string_value(row,
"birth_date");
1482 item.sex = get_string_value(row,
"sex");
1483 item.accession_no = get_string_value(row,
"accession_no");
1484 item.requested_proc_id = get_string_value(row,
"requested_proc_id");
1485 item.study_uid = get_string_value(row,
"study_uid");
1486 item.scheduled_datetime = get_string_value(row,
"scheduled_datetime");
1487 item.station_ae = get_string_value(row,
"station_ae");
1488 item.station_name = get_string_value(row,
"station_name");
1489 item.modality = get_string_value(row,
"modality");
1490 item.procedure_desc = get_string_value(row,
"procedure_desc");
1491 item.protocol_code = get_string_value(row,
"protocol_code");
1492 item.referring_phys = get_string_value(row,
"referring_phys");
1493 item.referring_phys_id = get_string_value(row,
"referring_phys_id");
1495 auto created_str = get_string_value(row,
"created_at");
1496 item.created_at = parse_datetime(created_str.c_str());
1498 auto updated_str = get_string_value(row,
"updated_at");
1499 item.updated_at = parse_datetime(updated_str.c_str());
1504auto index_database::parse_audit_from_row(
1505 const std::map<std::string, database::core::database_value>& row)
const
1507 audit_record record;
1509 record.pk = get_int64_value(row,
"audit_pk");
1510 record.event_type = get_string_value(row,
"event_type");
1511 record.outcome = get_string_value(row,
"outcome");
1513 auto timestamp_str = get_string_value(row,
"timestamp");
1514 record.timestamp = parse_datetime(timestamp_str.c_str());
1516 record.user_id = get_string_value(row,
"user_id");
1517 record.source_ae = get_string_value(row,
"source_ae");
1518 record.target_ae = get_string_value(row,
"target_ae");
1519 record.source_ip = get_string_value(row,
"source_ip");
1520 record.patient_id = get_string_value(row,
"patient_id");
1521 record.study_uid = get_string_value(row,
"study_uid");
1522 record.message = get_string_value(row,
"message");
1523 record.details = get_string_value(row,
"details");
1535 return worklist_repository_->add_worklist_item(item);
1539 std::string_view accession_no,
1540 std::string_view new_status)
1542 return worklist_repository_->update_worklist_status(step_id, accession_no,
1548 return worklist_repository_->query_worklist(query);
1552 std::string_view accession_no)
const
1553 -> std::optional<worklist_item> {
1554 return worklist_repository_->find_worklist_item(step_id, accession_no);
1558 -> std::optional<worklist_item> {
1559 return worklist_repository_->find_worklist_by_pk(pk);
1563 std::string_view accession_no)
1565 return worklist_repository_->delete_worklist_item(step_id, accession_no);
1570 return worklist_repository_->cleanup_old_worklist_items(age);
1574 std::chrono::system_clock::time_point before) ->
Result<size_t> {
1575 return worklist_repository_->cleanup_worklist_items_before(before);
1583 return worklist_repository_->worklist_count(status);
1587 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1590 item.pk = sqlite3_column_int64(stmt, 0);
1591 item.step_id = get_text(stmt, 1);
1592 item.step_status = get_text(stmt, 2);
1593 item.patient_id = get_text(stmt, 3);
1594 item.patient_name = get_text(stmt, 4);
1595 item.birth_date = get_text(stmt, 5);
1596 item.sex = get_text(stmt, 6);
1597 item.accession_no = get_text(stmt, 7);
1598 item.requested_proc_id = get_text(stmt, 8);
1599 item.study_uid = get_text(stmt, 9);
1600 item.scheduled_datetime = get_text(stmt, 10);
1601 item.station_ae = get_text(stmt, 11);
1602 item.station_name = get_text(stmt, 12);
1603 item.modality = get_text(stmt, 13);
1604 item.procedure_desc = get_text(stmt, 14);
1605 item.protocol_code = get_text(stmt, 15);
1606 item.referring_phys = get_text(stmt, 16);
1607 item.referring_phys_id = get_text(stmt, 17);
1609 auto created_str = get_text(stmt, 18);
1610 item.created_at = parse_datetime(created_str.c_str());
1612 auto updated_str = get_text(stmt, 19);
1613 item.updated_at = parse_datetime(updated_str.c_str());
1624 return ups_repository_->create_ups_workitem(workitem);
1629 return ups_repository_->update_ups_workitem(workitem);
1633 std::string_view new_state,
1634 std::string_view transaction_uid)
1636 return ups_repository_->change_ups_state(workitem_uid, new_state,
1641 -> std::optional<ups_workitem> {
1642 return ups_repository_->find_ups_workitem(workitem_uid);
1647 return ups_repository_->search_ups_workitems(query);
1652 return ups_repository_->delete_ups_workitem(workitem_uid);
1661 return ups_repository_->ups_workitem_count(state);
1670 return ups_repository_->subscribe_ups(subscription);
1674 std::string_view workitem_uid)
1676 return ups_repository_->unsubscribe_ups(subscriber_ae, workitem_uid);
1681 return ups_repository_->get_ups_subscriptions(subscriber_ae);
1686 return ups_repository_->get_ups_subscribers(workitem_uid);
1690 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1693 item.pk = sqlite3_column_int64(stmt, 0);
1694 item.workitem_uid = get_text(stmt, 1);
1695 item.state = get_text(stmt, 2);
1696 item.procedure_step_label = get_text(stmt, 3);
1697 item.worklist_label = get_text(stmt, 4);
1698 item.priority = get_text(stmt, 5);
1699 item.scheduled_start_datetime = get_text(stmt, 6);
1700 item.expected_completion_datetime = get_text(stmt, 7);
1701 item.scheduled_station_name = get_text(stmt, 8);
1702 item.scheduled_station_class = get_text(stmt, 9);
1703 item.scheduled_station_geographic = get_text(stmt, 10);
1704 item.scheduled_human_performers = get_text(stmt, 11);
1705 item.input_information = get_text(stmt, 12);
1706 item.performing_ae = get_text(stmt, 13);
1707 item.progress_description = get_text(stmt, 14);
1708 item.progress_percent = sqlite3_column_int(stmt, 15);
1709 item.output_information = get_text(stmt, 16);
1710 item.transaction_uid = get_text(stmt, 17);
1712 auto created_str = get_text(stmt, 18);
1713 item.created_at = parse_datetime(created_str.c_str());
1715 auto updated_str = get_text(stmt, 19);
1716 item.updated_at = parse_datetime(updated_str.c_str());
1727 return audit_repository_->add_audit_log(record);
1732 return audit_repository_->query_audit_log(query);
1736 -> std::optional<audit_record> {
1737 return audit_repository_->find_audit_by_pk(pk);
1746 return audit_repository_->cleanup_old_audit_logs(age);
1750 auto* stmt =
static_cast<sqlite3_stmt*
>(stmt_ptr);
1753 record.pk = sqlite3_column_int64(stmt, 0);
1754 record.event_type = get_text(stmt, 1);
1755 record.outcome = get_text(stmt, 2);
1757 auto timestamp_str = get_text(stmt, 3);
1758 record.timestamp = parse_datetime(timestamp_str.c_str());
1760 record.user_id = get_text(stmt, 4);
1761 record.source_ae = get_text(stmt, 5);
1762 record.target_ae = get_text(stmt, 6);
1763 record.source_ip = get_text(stmt, 7);
1764 record.patient_id = get_text(stmt, 8);
1765 record.study_uid = get_text(stmt, 9);
1766 record.message = get_text(stmt, 10);
1767 record.details = get_text(stmt, 11);
1772#ifdef PACS_WITH_DATABASE_SYSTEM
1773auto index_database::parse_patient_from_row(
1774 const std::map<std::string, database::core::database_value>& row)
const
1778 record.pk = get_int64_value(row,
"patient_pk");
1779 record.patient_id = get_string_value(row,
"patient_id");
1780 record.patient_name = get_string_value(row,
"patient_name");
1781 record.birth_date = get_string_value(row,
"birth_date");
1782 record.sex = get_string_value(row,
"sex");
1783 record.other_ids = get_string_value(row,
"other_ids");
1784 record.ethnic_group = get_string_value(row,
"ethnic_group");
1785 record.comments = get_string_value(row,
"comments");
1787 auto created_str = get_string_value(row,
"created_at");
1788 record.created_at = parse_datetime(created_str.c_str());
1790 auto updated_str = get_string_value(row,
"updated_at");
1791 record.updated_at = parse_datetime(updated_str.c_str());
1796auto index_database::parse_study_from_row(
1797 const std::map<std::string, database::core::database_value>& row)
const
1799 study_record record;
1801 record.pk = get_int64_value(row,
"study_pk");
1802 record.patient_pk = get_int64_value(row,
"patient_pk");
1803 record.study_uid = get_string_value(row,
"study_uid");
1804 record.study_id = get_string_value(row,
"study_id");
1805 record.study_date = get_string_value(row,
"study_date");
1806 record.study_time = get_string_value(row,
"study_time");
1807 record.accession_number = get_string_value(row,
"accession_number");
1808 record.referring_physician = get_string_value(row,
"referring_physician");
1809 record.study_description = get_string_value(row,
"study_description");
1810 record.modalities_in_study = get_string_value(row,
"modalities_in_study");
1811 record.num_series =
static_cast<int>(get_int64_value(row,
"num_series"));
1812 record.num_instances =
1813 static_cast<int>(get_int64_value(row,
"num_instances"));
1815 auto created_str = get_string_value(row,
"created_at");
1816 record.created_at = parse_datetime(created_str.c_str());
1818 auto updated_str = get_string_value(row,
"updated_at");
1819 record.updated_at = parse_datetime(updated_str.c_str());
1824auto index_database::parse_series_from_row(
1825 const std::map<std::string, database::core::database_value>& row)
const
1827 series_record record;
1829 record.pk = get_int64_value(row,
"series_pk");
1830 record.study_pk = get_int64_value(row,
"study_pk");
1831 record.series_uid = get_string_value(row,
"series_uid");
1832 record.modality = get_string_value(row,
"modality");
1835 auto it = row.find(
"series_number");
1836 if (it != row.end()) {
1837 if (std::holds_alternative<int64_t>(it->second)) {
1838 record.series_number =
static_cast<int>(std::get<int64_t>(it->second));
1839 }
else if (std::holds_alternative<std::string>(it->second)) {
1840 const auto& str = std::get<std::string>(it->second);
1842 record.series_number = std::stoi(str);
1847 record.series_description = get_string_value(row,
"series_description");
1848 record.body_part_examined = get_string_value(row,
"body_part_examined");
1849 record.station_name = get_string_value(row,
"station_name");
1851 static_cast<int>(get_int64_value(row,
"num_instances"));
1853 auto created_str = get_string_value(row,
"created_at");
1854 record.created_at = parse_datetime(created_str.c_str());
1856 auto updated_str2 = get_string_value(row,
"updated_at");
1857 record.updated_at = parse_datetime(updated_str2.c_str());
1862auto index_database::parse_instance_from_row(
1863 const std::map<std::string, database::core::database_value>& row)
const
1864 -> instance_record {
1867 record.pk = get_int64_value(row,
"instance_pk");
1868 record.series_pk = get_int64_value(row,
"series_pk");
1869 record.sop_uid = get_string_value(row,
"sop_uid");
1870 record.sop_class_uid = get_string_value(row,
"sop_class_uid");
1873 auto it = row.find(
"instance_number");
1874 if (it != row.end()) {
1875 if (std::holds_alternative<int64_t>(it->second)) {
1876 record.instance_number =
static_cast<int>(std::get<int64_t>(it->second));
1877 }
else if (std::holds_alternative<std::string>(it->second)) {
1878 const auto& str = std::get<std::string>(it->second);
1880 record.instance_number = std::stoi(str);
1885 record.transfer_syntax = get_string_value(row,
"transfer_syntax");
1886 record.content_date = get_string_value(row,
"content_date");
1887 record.content_time = get_string_value(row,
"content_time");
1890 auto rows_it = row.find(
"rows");
1891 if (rows_it != row.end()) {
1892 if (std::holds_alternative<int64_t>(rows_it->second)) {
1893 record.rows =
static_cast<int>(std::get<int64_t>(rows_it->second));
1894 }
else if (std::holds_alternative<std::string>(rows_it->second)) {
1895 const auto& str = std::get<std::string>(rows_it->second);
1897 record.rows = std::stoi(str);
1902 auto cols_it = row.find(
"columns");
1903 if (cols_it != row.end()) {
1904 if (std::holds_alternative<int64_t>(cols_it->second)) {
1905 record.columns =
static_cast<int>(std::get<int64_t>(cols_it->second));
1906 }
else if (std::holds_alternative<std::string>(cols_it->second)) {
1907 const auto& str = std::get<std::string>(cols_it->second);
1909 record.columns = std::stoi(str);
1914 auto bits_it = row.find(
"bits_allocated");
1915 if (bits_it != row.end()) {
1916 if (std::holds_alternative<int64_t>(bits_it->second)) {
1917 record.bits_allocated =
static_cast<int>(std::get<int64_t>(bits_it->second));
1918 }
else if (std::holds_alternative<std::string>(bits_it->second)) {
1919 const auto& str = std::get<std::string>(bits_it->second);
1921 record.bits_allocated = std::stoi(str);
1926 auto frames_it = row.find(
"number_of_frames");
1927 if (frames_it != row.end()) {
1928 if (std::holds_alternative<int64_t>(frames_it->second)) {
1929 record.number_of_frames =
static_cast<int>(std::get<int64_t>(frames_it->second));
1930 }
else if (std::holds_alternative<std::string>(frames_it->second)) {
1931 const auto& str = std::get<std::string>(frames_it->second);
1933 record.number_of_frames = std::stoi(str);
1938 record.file_path = get_string_value(row,
"file_path");
1939 record.file_size = get_int64_value(row,
"file_size");
1940 record.file_hash = get_string_value(row,
"file_hash");
1942 auto created_str = get_string_value(row,
"created_at");
1943 record.created_at = parse_datetime(created_str.c_str());
Repository for audit log persistence.
PACS index database manager.
std::shared_ptr< mpps_repository > mpps_repository_
auto find_patient(std::string_view patient_id) const -> std::optional< patient_record >
Find a patient by patient ID.
auto cleanup_old_audit_logs(std::chrono::hours age) -> Result< size_t >
Cleanup old audit log entries.
auto list_active_mpps(std::string_view station_ae) const -> Result< std::vector< mpps_record > >
List active (IN PROGRESS) MPPS records for a station.
auto find_patient_by_pk(int64_t pk) const -> std::optional< patient_record >
Find a patient by primary key.
auto verify_integrity() const -> VoidResult
Verify database integrity.
auto patient_count() const -> Result< size_t >
Get total patient count.
auto parse_series_row(void *stmt) const -> series_record
Parse a series record from a prepared statement.
auto vacuum() -> VoidResult
Reclaim unused space in the database.
auto delete_series(std::string_view series_uid) -> VoidResult
Delete a series by Series Instance UID.
auto get_ups_subscriptions(std::string_view subscriber_ae) const -> Result< std::vector< ups_subscription > >
Get all subscriptions for a subscriber.
index_database(const index_database &)=delete
auto find_instance_by_pk(int64_t pk) const -> std::optional< instance_record >
Find an instance by primary key.
auto find_ups_workitem(std::string_view workitem_uid) const -> std::optional< ups_workitem >
Find a UPS workitem by SOP Instance UID.
auto parse_study_row(void *stmt) const -> study_record
Parse a study record from a prepared statement.
auto find_study_by_pk(int64_t pk) const -> std::optional< study_record >
Find a study by primary key.
auto study_count() const -> Result< size_t >
Get total study count.
auto find_audit_by_pk(int64_t pk) const -> std::optional< audit_record >
Find an audit log entry by primary key.
auto list_instances(std::string_view series_uid) const -> Result< std::vector< instance_record > >
List all instances for a series.
auto search_ups_workitems(const ups_workitem_query &query) const -> Result< std::vector< ups_workitem > >
Search UPS workitems with query criteria.
auto search_mpps(const mpps_query &query) const -> Result< std::vector< mpps_record > >
Search MPPS records with query criteria.
auto get_ups_subscribers(std::string_view workitem_uid) const -> Result< std::vector< std::string > >
Get all subscribers for a workitem.
std::shared_ptr< audit_repository > audit_repository_
auto list_series(std::string_view study_uid) const -> Result< std::vector< series_record > >
List all series for a study.
std::shared_ptr< patient_repository > patient_repository_
Extracted repositories used by the facade API.
auto find_series_by_pk(int64_t pk) const -> std::optional< series_record >
Find a series by primary key.
auto query_audit_log(const audit_query &query) const -> Result< std::vector< audit_record > >
Query audit log entries.
std::shared_ptr< study_repository > study_repository_
auto find_mpps_by_study(std::string_view study_uid) const -> Result< std::vector< mpps_record > >
Find MPPS records by Study Instance UID.
std::shared_ptr< worklist_repository > worklist_repository_
auto find_worklist_item(std::string_view step_id, std::string_view accession_no) const -> std::optional< worklist_item >
Find a worklist item by step ID and accession number.
auto operator=(const index_database &) -> index_database &=delete
std::shared_ptr< ups_repository > ups_repository_
auto update_modalities_in_study(int64_t study_pk) -> VoidResult
Update modalities in study (denormalized field)
auto upsert_instance(int64_t series_pk, std::string_view sop_uid, std::string_view sop_class_uid, std::string_view file_path, int64_t file_size, std::string_view transfer_syntax="", std::optional< int > instance_number=std::nullopt) -> Result< int64_t >
Insert or update an instance record.
auto delete_ups_workitem(std::string_view workitem_uid) -> VoidResult
Delete a UPS workitem.
auto search_studies(const study_query &query) const -> Result< std::vector< study_record > >
Search studies with query criteria.
auto parse_ups_workitem_row(void *stmt) const -> ups_workitem
Parse a UPS workitem record from a prepared statement.
std::shared_ptr< instance_repository > instance_repository_
auto parse_mpps_row(void *stmt) const -> mpps_record
Parse an MPPS record from a prepared statement.
auto delete_worklist_item(std::string_view step_id, std::string_view accession_no) -> VoidResult
Delete a worklist item.
auto parse_instance_row(void *stmt) const -> instance_record
Parse an instance record from a prepared statement.
auto get_series_files(std::string_view series_instance_uid) const -> Result< std::vector< std::string > >
Get all file paths for a series.
auto parse_worklist_row(void *stmt) const -> worklist_item
Parse a worklist record from a prepared statement.
auto delete_patient(std::string_view patient_id) -> VoidResult
Delete a patient by patient ID.
auto series_count() const -> Result< size_t >
Get total series count.
auto native_handle() const noexcept -> sqlite3 *
Get the raw SQLite database handle.
auto create_mpps(std::string_view mpps_uid, std::string_view station_ae="", std::string_view modality="", std::string_view study_uid="", std::string_view accession_no="", std::string_view start_datetime="") -> Result< int64_t >
Create a new MPPS record (N-CREATE)
auto is_open() const noexcept -> bool
Check if the database is open.
auto upsert_patient(std::string_view patient_id, std::string_view patient_name="", std::string_view birth_date="", std::string_view sex="") -> Result< int64_t >
Insert or update a patient record.
static auto open(std::string_view db_path) -> Result< std::unique_ptr< index_database > >
Open or create a database with default configuration.
auto parse_audit_row(void *stmt) const -> audit_record
Parse an audit record from a prepared statement.
auto ups_workitem_count() const -> Result< size_t >
Get total UPS workitem count.
auto subscribe_ups(const ups_subscription &subscription) -> Result< int64_t >
Subscribe to UPS workitem events.
auto add_audit_log(const audit_record &record) -> Result< int64_t >
Add a new audit log entry.
auto upsert_series(int64_t study_pk, std::string_view series_uid, std::string_view modality="", std::optional< int > series_number=std::nullopt, std::string_view series_description="", std::string_view body_part_examined="", std::string_view station_name="") -> Result< int64_t >
Insert or update a series record.
auto audit_count() const -> Result< size_t >
Get total audit log count.
auto update_worklist_status(std::string_view step_id, std::string_view accession_no, std::string_view new_status) -> VoidResult
Update worklist item status.
auto analyze() -> VoidResult
Update database statistics for query optimization.
auto cleanup_old_worklist_items(std::chrono::hours age) -> Result< size_t >
Cleanup old worklist items.
auto list_studies(std::string_view patient_id) const -> Result< std::vector< study_record > >
List all studies for a patient.
auto update_ups_workitem(const ups_workitem &workitem) -> VoidResult
Update an existing UPS workitem.
std::string path_
Database file path.
auto change_ups_state(std::string_view workitem_uid, std::string_view new_state, std::string_view transaction_uid="") -> VoidResult
Change UPS workitem state.
auto find_study(std::string_view study_uid) const -> std::optional< study_record >
Find a study by Study Instance UID.
auto update_mpps(std::string_view mpps_uid, std::string_view new_status, std::string_view end_datetime="", std::string_view performed_series="") -> VoidResult
Update an existing MPPS record (N-SET)
auto query_worklist(const worklist_query &query) const -> Result< std::vector< worklist_item > >
Query worklist items.
auto worklist_count() const -> Result< size_t >
Get total worklist count.
bool remove_on_close_
Remove the backing file on destruction for adapter-compatible memory DBs.
auto parse_patient_row(void *stmt) const -> patient_record
Parse a patient record from a prepared statement.
auto path() const -> std::string_view
Get the database file path.
auto delete_mpps(std::string_view mpps_uid) -> VoidResult
Delete an MPPS record.
auto search_instances(const instance_query &query) const -> Result< std::vector< instance_record > >
Search instances with query criteria.
auto unsubscribe_ups(std::string_view subscriber_ae, std::string_view workitem_uid="") -> VoidResult
Unsubscribe from UPS workitem events.
auto find_worklist_by_pk(int64_t pk) const -> std::optional< worklist_item >
Find a worklist item by primary key.
auto find_series(std::string_view series_uid) const -> std::optional< series_record >
Find a series by Series Instance UID.
auto create_ups_workitem(const ups_workitem &workitem) -> Result< int64_t >
Create a new UPS workitem (N-CREATE)
auto get_study_files(std::string_view study_instance_uid) const -> Result< std::vector< std::string > >
Get all file paths for a study.
auto instance_count() const -> Result< size_t >
Get total instance count.
migration_runner migration_runner_
Migration runner for schema management.
auto search_series(const series_query &query) const -> Result< std::vector< series_record > >
Search series with query criteria.
auto find_instance(std::string_view sop_uid) const -> std::optional< instance_record >
Find an instance by SOP Instance UID.
auto get_file_path(std::string_view sop_instance_uid) const -> Result< std::optional< std::string > >
Get file path for a SOP Instance UID.
auto delete_study(std::string_view study_uid) -> VoidResult
Delete a study by Study Instance UID.
auto checkpoint(bool truncate=false) -> VoidResult
Checkpoint WAL file.
auto schema_version() const -> int
Get the current schema version.
auto delete_instance(std::string_view sop_uid) -> VoidResult
Delete an instance by SOP Instance UID.
sqlite3 * db_
SQLite database handle (used for migrations and fallback)
auto cleanup_worklist_items_before(std::chrono::system_clock::time_point before) -> Result< size_t >
Cleanup worklist items scheduled before a specific time.
auto find_mpps_by_pk(int64_t pk) const -> std::optional< mpps_record >
Find an MPPS by primary key.
auto add_worklist_item(const worklist_item &item) -> Result< int64_t >
Add a new worklist item.
std::shared_ptr< series_repository > series_repository_
auto mpps_count() const -> Result< size_t >
Get total MPPS count.
auto search_patients(const patient_query &query) const -> Result< std::vector< patient_record > >
Search patients with query criteria.
static auto to_like_pattern(std::string_view pattern) -> std::string
Convert wildcard pattern to SQL LIKE pattern.
auto get_storage_stats() const -> Result< storage_stats >
Get storage statistics.
auto upsert_study(int64_t patient_pk, std::string_view study_uid, std::string_view study_id="", std::string_view study_date="", std::string_view study_time="", std::string_view accession_number="", std::string_view referring_physician="", std::string_view study_description="") -> Result< int64_t >
Insert or update a study record.
~index_database()
Destructor - closes database connection.
auto initialize_repositories() -> VoidResult
Initialize extracted repositories for facade delegation.
auto find_mpps(std::string_view mpps_uid) const -> std::optional< mpps_record >
Find an MPPS by SOP Instance UID.
auto get_current_version(sqlite3 *db) const -> int
Get the current schema version.
PACS index database for metadata storage and retrieval.
Repository for instance metadata persistence using base_repository pattern.
Repository for MPPS lifecycle persistence using base_repository pattern.
constexpr int database_query_error
constexpr int database_connection_error
@ counter
Monotonic increasing value.
@ record
RECORD - Treatment record dose.
Result< T > pacs_error(int code, const std::string &message, const std::string &details="")
Create a PACS error result with module context.
Repository for patient metadata persistence using base_repository pattern.
Result<T> type aliases and helpers for PACS system.
Repository for series metadata persistence using base_repository pattern.
Query parameters for audit log search.
Audit log record from the database.
Configuration for index database.
Storage statistics structure.
size_t total_instances
Total number of instances.
int64_t total_file_size
Total size of all files in bytes.
size_t total_series
Total number of series.
size_t total_studies
Total number of studies.
int64_t database_size
Size of the database file in bytes.
size_t total_patients
Total number of patients.
Query parameters for instance search.
Instance record from the database.
Result of a migration operation.
Query parameters for MPPS search.
MPPS record from the database.
Query parameters for patient search.
Patient record from the database.
Query parameters for series search.
Series record from the database.
Query parameters for study search.
Study record from the database.
UPS subscription record from the database.
Query parameters for UPS workitem search.
UPS workitem record from the database.
Worklist item record from the database.
Query parameters for worklist search.
Repository for study metadata persistence using base_repository pattern.
Compatibility header for cross-platform time functions.
Repository for UPS lifecycle and subscription persistence.
Repository for modality worklist persistence using base_repository pattern.